diff --git a/.circleci/config.yml b/.circleci/config.yml index 9f750a6e5..77a8d006b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -30,49 +30,52 @@ jobs: command: mkdir -p .buildcache && echo "*" > .buildcache/.gitignore name: Ignore .buildcache - restore_cache: - key: package-d31a4f1f3856b35a8a0c1dd5eb6a34f62026530f-{{checksum ".buildcache/cache-keys/package-d31a4f1f3856b35a8a0c1dd5eb6a34f62026530f"}} + key: package-5e182eecadd802ce4fde19b40733c21f1920d680-{{checksum ".buildcache/cache-keys/package-5e182eecadd802ce4fde19b40733c21f1920d680"}} name: Restore package cache - restore_cache: - key: package-a315309198e0c87a3964c4a0514206e1a1601de5-{{checksum ".buildcache/cache-keys/package-a315309198e0c87a3964c4a0514206e1a1601de5"}} + key: package-72ea9453ed75bfab19ee6a1fd34e7ac751e5e71d-{{checksum ".buildcache/cache-keys/package-72ea9453ed75bfab19ee6a1fd34e7ac751e5e71d"}} name: Restore package cache - restore_cache: - key: package-7b4b394a2ac479c3d6d9b0aa24b944a3bb01e292-{{checksum ".buildcache/cache-keys/package-7b4b394a2ac479c3d6d9b0aa24b944a3bb01e292"}} + key: package-ccae35a6ffa3283396cbe5608aa626a65cf951f4-{{checksum ".buildcache/cache-keys/package-ccae35a6ffa3283396cbe5608aa626a65cf951f4"}} name: Restore package cache - restore_cache: - key: package-6f96cd412f008849de6529e990e5b46f75085f0e-{{checksum ".buildcache/cache-keys/package-6f96cd412f008849de6529e990e5b46f75085f0e"}} + key: package-0101b0c84138c8e37a5affd1171d04237dcd044f-{{checksum ".buildcache/cache-keys/package-0101b0c84138c8e37a5affd1171d04237dcd044f"}} name: Restore package cache - restore_cache: - key: package-36979f31c13248a027ff8866979f2aa282c9f9d6-{{checksum ".buildcache/cache-keys/package-36979f31c13248a027ff8866979f2aa282c9f9d6"}} + key: package-3735bcca8df7adfefa7d79ae87bb6731ed5e074f-{{checksum ".buildcache/cache-keys/package-3735bcca8df7adfefa7d79ae87bb6731ed5e074f"}} name: Restore package cache - restore_cache: - key: package-219d6c3eddb2481810202dccfc71a8467e4a077a-{{checksum ".buildcache/cache-keys/package-219d6c3eddb2481810202dccfc71a8467e4a077a"}} + key: package-81b9112a9e1bd1d33778c4673adbec942c595a7e-{{checksum ".buildcache/cache-keys/package-81b9112a9e1bd1d33778c4673adbec942c595a7e"}} name: Restore package cache - restore_cache: - key: package-1a1ebfab53158923bc4779535ebd667ff8ed5ca3-{{checksum ".buildcache/cache-keys/package-1a1ebfab53158923bc4779535ebd667ff8ed5ca3"}} + key: package-b04f52247addbb91ec89a35be1ddff9feadf7090-{{checksum ".buildcache/cache-keys/package-b04f52247addbb91ec89a35be1ddff9feadf7090"}} name: Restore package cache - restore_cache: - key: package-100f99d19a296ddc4ba797c583b4c68266d61c52-{{checksum ".buildcache/cache-keys/package-100f99d19a296ddc4ba797c583b4c68266d61c52"}} + key: package-25033317f5db33c76740dba3e792063240c317b1-{{checksum ".buildcache/cache-keys/package-25033317f5db33c76740dba3e792063240c317b1"}} name: Restore package cache - restore_cache: - key: package-0ab42b4e27fc1be0012f56be6566d1f79416efc4-{{checksum ".buildcache/cache-keys/package-0ab42b4e27fc1be0012f56be6566d1f79416efc4"}} + key: package-dc42bacf89f0d78468257a40c404e141fa9272c8-{{checksum ".buildcache/cache-keys/package-dc42bacf89f0d78468257a40c404e141fa9272c8"}} name: Restore package cache - restore_cache: - key: package-f6ec6dce632eda1d6b6fc812702ddbfc78b00209-{{checksum ".buildcache/cache-keys/package-f6ec6dce632eda1d6b6fc812702ddbfc78b00209"}} + key: package-f56a0ca98b1bf7e4f647cedee7db20d8b940a1e0-{{checksum ".buildcache/cache-keys/package-f56a0ca98b1bf7e4f647cedee7db20d8b940a1e0"}} name: Restore package cache - restore_cache: - key: package-db956b3de630063891714da55d71c00aee11a868-{{checksum ".buildcache/cache-keys/package-db956b3de630063891714da55d71c00aee11a868"}} + key: package-8dd295cf186df50e92ed66575b73e97003885428-{{checksum ".buildcache/cache-keys/package-8dd295cf186df50e92ed66575b73e97003885428"}} name: Restore package cache - restore_cache: - key: package-1c616d887a6ba6f72bf57aaae02b62cfe2260056-{{checksum ".buildcache/cache-keys/package-1c616d887a6ba6f72bf57aaae02b62cfe2260056"}} + key: package-997f9a9e448c02fe17505a5b75635b9dd1104d92-{{checksum ".buildcache/cache-keys/package-997f9a9e448c02fe17505a5b75635b9dd1104d92"}} name: Restore package cache - restore_cache: - key: package-02bf7eae9ab7517d616b442655b0db880a355a5e-{{checksum ".buildcache/cache-keys/package-02bf7eae9ab7517d616b442655b0db880a355a5e"}} + key: package-505ab0aca6b881919f05bb8e0125783a5799b4fc-{{checksum ".buildcache/cache-keys/package-505ab0aca6b881919f05bb8e0125783a5799b4fc"}} name: Restore package cache - restore_cache: - key: package-e57134ec78767637844ee933e9c9a58bd3a24182-{{checksum ".buildcache/cache-keys/package-e57134ec78767637844ee933e9c9a58bd3a24182"}} + key: package-b7d03bef8d16c702d595798df0cbda643915292c-{{checksum ".buildcache/cache-keys/package-b7d03bef8d16c702d595798df0cbda643915292c"}} name: Restore package cache - restore_cache: - key: package-60c90459d2d4978488819b2ca4118ba602308095-{{checksum ".buildcache/cache-keys/package-60c90459d2d4978488819b2ca4118ba602308095"}} + key: package-a68848e3db3953d016f73aabb3d732a0333b7bc9-{{checksum ".buildcache/cache-keys/package-a68848e3db3953d016f73aabb3d732a0333b7bc9"}} + name: Restore package cache + - restore_cache: + key: package-89694dc28c6ddc1d0648b69cefdcc9f67d59a240-{{checksum ".buildcache/cache-keys/package-89694dc28c6ddc1d0648b69cefdcc9f67d59a240"}} name: Restore package cache - run: command: make package-meta-all @@ -84,20 +87,20 @@ jobs: command: ls -lahR .buildcache name: List Build Cache - run: - command: cp packages*.lock/pkgs.yml lockfile-8551e55f6857ec68.yml + command: cp packages*.lock/pkgs.yml lockfile-7f1510c592a3019a.yml name: Update Lockfile Name - run: - command: tar -czf packages-8551e55f6857ec68.tar.gz .buildcache/packages lockfile-8551e55f6857ec68.yml + command: tar -czf packages-7f1510c592a3019a.tar.gz .buildcache/packages lockfile-7f1510c592a3019a.yml name: Create Raw Package Tarball - run: - command: tar -czf meta-8551e55f6857ec68.tar.gz .buildcache/packages/store/*.json lockfile-8551e55f6857ec68.yml + command: tar -czf meta-7f1510c592a3019a.tar.gz .buildcache/packages/store/*.json lockfile-7f1510c592a3019a.yml name: Create Metadata Tarball - store_artifacts: - path: lockfile-8551e55f6857ec68.yml + path: lockfile-7f1510c592a3019a.yml - store_artifacts: - path: packages-8551e55f6857ec68.tar.gz + path: packages-7f1510c592a3019a.tar.gz - store_artifacts: - path: meta-8551e55f6857ec68.tar.gz + path: meta-7f1510c592a3019a.tar.gz - store_artifacts: path: .buildcache/packages environment: @@ -112,7 +115,7 @@ jobs: - AUTO_INSTALL_TOOLS: 'YES' - BUILDKIT_PROGRESS: plain - PRODUCT_REVISION: '' - - PACKAGE_SPEC_ID: d31a4f1f3856b35a8a0c1dd5eb6a34f62026530f + - PACKAGE_SPEC_ID: 5e182eecadd802ce4fde19b40733c21f1920d680 steps: - setup_remote_docker: docker_layer_caching: false @@ -126,7 +129,7 @@ jobs: command: make -C packages*.lock write-package-cache-key name: Write package cache key - restore_cache: - key: package-d31a4f1f3856b35a8a0c1dd5eb6a34f62026530f-{{checksum ".buildcache/cache-keys/package-d31a4f1f3856b35a8a0c1dd5eb6a34f62026530f"}} + key: package-5e182eecadd802ce4fde19b40733c21f1920d680-{{checksum ".buildcache/cache-keys/package-5e182eecadd802ce4fde19b40733c21f1920d680"}} name: Restore package cache - run: command: |2 @@ -172,7 +175,79 @@ jobs: command: ls -lahR .buildcache/packages name: List packages - save_cache: - key: package-d31a4f1f3856b35a8a0c1dd5eb6a34f62026530f-{{checksum ".buildcache/cache-keys/package-d31a4f1f3856b35a8a0c1dd5eb6a34f62026530f"}} + key: package-5e182eecadd802ce4fde19b40733c21f1920d680-{{checksum ".buildcache/cache-keys/package-5e182eecadd802ce4fde19b40733c21f1920d680"}} + name: Save package cache + paths: + - .buildcache/packages/store + darwin_arm64_package: + docker: + - image: docker.mirror.hashicorp.services/circleci/buildpack-deps + shell: /usr/bin/env bash -euo pipefail -c + environment: + - AUTO_INSTALL_TOOLS: 'YES' + - BUILDKIT_PROGRESS: plain + - PRODUCT_REVISION: '' + - PACKAGE_SPEC_ID: 72ea9453ed75bfab19ee6a1fd34e7ac751e5e71d + steps: + - setup_remote_docker: + docker_layer_caching: false + version: 19.03.12 + - add_ssh_keys: + fingerprints: + - 0e:03:77:f4:e2:c3:56:c2:53:6a:03:e1:31:91:2f:06 + - checkout: + path: . + - run: + command: make -C packages*.lock write-package-cache-key + name: Write package cache key + - restore_cache: + key: package-72ea9453ed75bfab19ee6a1fd34e7ac751e5e71d-{{checksum ".buildcache/cache-keys/package-72ea9453ed75bfab19ee6a1fd34e7ac751e5e71d"}} + name: Restore package cache + - run: + command: |2 + + if ! { PKG=$(find .buildcache/packages/store -maxdepth 1 -mindepth 1 -name '*.zip' 2> /dev/null) && [ -n "$PKG" ]; }; then + echo "No package found, continuing with build." + exit 0 + fi + echo "Package already cached, skipping build." + circleci-agent step halt + name: Check cache status + - run: + command: make -C packages*.lock write-builder-cache-keys + name: Write builder layer cache keys + - restore_cache: + key: copy-source_6be6_{{checksum ".buildcache/cache-keys/copy-source-6be6bbcf469c2d26620194eeee87f0c4ba470bde"}} + keys: + - go-modules_74c0_{{checksum ".buildcache/cache-keys/go-modules-74c0c94188690e22d738cb1784f8feba7c3dc00d"}} + - ui_dfc9_{{checksum ".buildcache/cache-keys/ui-dfc937906b9b645655c44bc782ef13c64ebe8dbb"}} + - base_cc87_{{checksum ".buildcache/cache-keys/base-cc87158f920a83b1c241d68474eed6734b32c511"}} + name: 'Restore Builder Image Cache: copy-source' + - run: + command: make -C packages*.lock load-builder-cache + name: Load whatever builder cache we have (if any) into the Docker daemon + no_output_timeout: 30m + - run: + command: |2- + + KEYFILE="$HOME/.ssh/id_rsa_c6969882dc046c39ddac8305e3151c98" + eval "$(ssh-agent -s)" + if [ -f "$KEYFILE" ]; then + ssh-add "$KEYFILE" + else + echo "==> INFO: SSH key for github.com not found" + echo " Attempts to access private repositories from within" + echo " the build will fail, e.g. for private go modules, or" + echo " attempts to directly clone private repositories." + fi + + make -C packages*.lock package + name: Compile Package + - run: + command: ls -lahR .buildcache/packages + name: List packages + - save_cache: + key: package-72ea9453ed75bfab19ee6a1fd34e7ac751e5e71d-{{checksum ".buildcache/cache-keys/package-72ea9453ed75bfab19ee6a1fd34e7ac751e5e71d"}} name: Save package cache paths: - .buildcache/packages/store @@ -184,7 +259,7 @@ jobs: - AUTO_INSTALL_TOOLS: 'YES' - BUILDKIT_PROGRESS: plain - PRODUCT_REVISION: '' - - PACKAGE_SPEC_ID: e57134ec78767637844ee933e9c9a58bd3a24182 + - PACKAGE_SPEC_ID: a68848e3db3953d016f73aabb3d732a0333b7bc9 steps: - setup_remote_docker: docker_layer_caching: false @@ -198,7 +273,7 @@ jobs: command: make -C packages*.lock write-package-cache-key name: Write package cache key - restore_cache: - key: package-e57134ec78767637844ee933e9c9a58bd3a24182-{{checksum ".buildcache/cache-keys/package-e57134ec78767637844ee933e9c9a58bd3a24182"}} + key: package-a68848e3db3953d016f73aabb3d732a0333b7bc9-{{checksum ".buildcache/cache-keys/package-a68848e3db3953d016f73aabb3d732a0333b7bc9"}} name: Restore package cache - run: command: |2 @@ -244,7 +319,7 @@ jobs: command: ls -lahR .buildcache/packages name: List packages - save_cache: - key: package-e57134ec78767637844ee933e9c9a58bd3a24182-{{checksum ".buildcache/cache-keys/package-e57134ec78767637844ee933e9c9a58bd3a24182"}} + key: package-a68848e3db3953d016f73aabb3d732a0333b7bc9-{{checksum ".buildcache/cache-keys/package-a68848e3db3953d016f73aabb3d732a0333b7bc9"}} name: Save package cache paths: - .buildcache/packages/store @@ -380,7 +455,7 @@ jobs: - AUTO_INSTALL_TOOLS: 'YES' - BUILDKIT_PROGRESS: plain - PRODUCT_REVISION: '' - - PACKAGE_SPEC_ID: 100f99d19a296ddc4ba797c583b4c68266d61c52 + - PACKAGE_SPEC_ID: dc42bacf89f0d78468257a40c404e141fa9272c8 steps: - setup_remote_docker: docker_layer_caching: false @@ -394,7 +469,7 @@ jobs: command: make -C packages*.lock write-package-cache-key name: Write package cache key - restore_cache: - key: package-100f99d19a296ddc4ba797c583b4c68266d61c52-{{checksum ".buildcache/cache-keys/package-100f99d19a296ddc4ba797c583b4c68266d61c52"}} + key: package-dc42bacf89f0d78468257a40c404e141fa9272c8-{{checksum ".buildcache/cache-keys/package-dc42bacf89f0d78468257a40c404e141fa9272c8"}} name: Restore package cache - run: command: |2 @@ -440,7 +515,7 @@ jobs: command: ls -lahR .buildcache/packages name: List packages - save_cache: - key: package-100f99d19a296ddc4ba797c583b4c68266d61c52-{{checksum ".buildcache/cache-keys/package-100f99d19a296ddc4ba797c583b4c68266d61c52"}} + key: package-dc42bacf89f0d78468257a40c404e141fa9272c8-{{checksum ".buildcache/cache-keys/package-dc42bacf89f0d78468257a40c404e141fa9272c8"}} name: Save package cache paths: - .buildcache/packages/store @@ -452,7 +527,7 @@ jobs: - AUTO_INSTALL_TOOLS: 'YES' - BUILDKIT_PROGRESS: plain - PRODUCT_REVISION: '' - - PACKAGE_SPEC_ID: 1a1ebfab53158923bc4779535ebd667ff8ed5ca3 + - PACKAGE_SPEC_ID: 25033317f5db33c76740dba3e792063240c317b1 steps: - setup_remote_docker: docker_layer_caching: false @@ -466,7 +541,7 @@ jobs: command: make -C packages*.lock write-package-cache-key name: Write package cache key - restore_cache: - key: package-1a1ebfab53158923bc4779535ebd667ff8ed5ca3-{{checksum ".buildcache/cache-keys/package-1a1ebfab53158923bc4779535ebd667ff8ed5ca3"}} + key: package-25033317f5db33c76740dba3e792063240c317b1-{{checksum ".buildcache/cache-keys/package-25033317f5db33c76740dba3e792063240c317b1"}} name: Restore package cache - run: command: |2 @@ -512,7 +587,7 @@ jobs: command: ls -lahR .buildcache/packages name: List packages - save_cache: - key: package-1a1ebfab53158923bc4779535ebd667ff8ed5ca3-{{checksum ".buildcache/cache-keys/package-1a1ebfab53158923bc4779535ebd667ff8ed5ca3"}} + key: package-25033317f5db33c76740dba3e792063240c317b1-{{checksum ".buildcache/cache-keys/package-25033317f5db33c76740dba3e792063240c317b1"}} name: Save package cache paths: - .buildcache/packages/store @@ -535,7 +610,7 @@ jobs: - AUTO_INSTALL_TOOLS: 'YES' - BUILDKIT_PROGRESS: plain - PRODUCT_REVISION: '' - - PACKAGE_SPEC_ID: 36979f31c13248a027ff8866979f2aa282c9f9d6 + - PACKAGE_SPEC_ID: 81b9112a9e1bd1d33778c4673adbec942c595a7e steps: - setup_remote_docker: docker_layer_caching: false @@ -549,7 +624,7 @@ jobs: command: make -C packages*.lock write-package-cache-key name: Write package cache key - restore_cache: - key: package-36979f31c13248a027ff8866979f2aa282c9f9d6-{{checksum ".buildcache/cache-keys/package-36979f31c13248a027ff8866979f2aa282c9f9d6"}} + key: package-81b9112a9e1bd1d33778c4673adbec942c595a7e-{{checksum ".buildcache/cache-keys/package-81b9112a9e1bd1d33778c4673adbec942c595a7e"}} name: Restore package cache - run: command: |2 @@ -595,7 +670,7 @@ jobs: command: ls -lahR .buildcache/packages name: List packages - save_cache: - key: package-36979f31c13248a027ff8866979f2aa282c9f9d6-{{checksum ".buildcache/cache-keys/package-36979f31c13248a027ff8866979f2aa282c9f9d6"}} + key: package-81b9112a9e1bd1d33778c4673adbec942c595a7e-{{checksum ".buildcache/cache-keys/package-81b9112a9e1bd1d33778c4673adbec942c595a7e"}} name: Save package cache paths: - .buildcache/packages/store @@ -607,7 +682,7 @@ jobs: - AUTO_INSTALL_TOOLS: 'YES' - BUILDKIT_PROGRESS: plain - PRODUCT_REVISION: '' - - PACKAGE_SPEC_ID: a315309198e0c87a3964c4a0514206e1a1601de5 + - PACKAGE_SPEC_ID: ccae35a6ffa3283396cbe5608aa626a65cf951f4 steps: - setup_remote_docker: docker_layer_caching: false @@ -621,7 +696,7 @@ jobs: command: make -C packages*.lock write-package-cache-key name: Write package cache key - restore_cache: - key: package-a315309198e0c87a3964c4a0514206e1a1601de5-{{checksum ".buildcache/cache-keys/package-a315309198e0c87a3964c4a0514206e1a1601de5"}} + key: package-ccae35a6ffa3283396cbe5608aa626a65cf951f4-{{checksum ".buildcache/cache-keys/package-ccae35a6ffa3283396cbe5608aa626a65cf951f4"}} name: Restore package cache - run: command: |2 @@ -667,7 +742,7 @@ jobs: command: ls -lahR .buildcache/packages name: List packages - save_cache: - key: package-a315309198e0c87a3964c4a0514206e1a1601de5-{{checksum ".buildcache/cache-keys/package-a315309198e0c87a3964c4a0514206e1a1601de5"}} + key: package-ccae35a6ffa3283396cbe5608aa626a65cf951f4-{{checksum ".buildcache/cache-keys/package-ccae35a6ffa3283396cbe5608aa626a65cf951f4"}} name: Save package cache paths: - .buildcache/packages/store @@ -679,7 +754,7 @@ jobs: - AUTO_INSTALL_TOOLS: 'YES' - BUILDKIT_PROGRESS: plain - PRODUCT_REVISION: '' - - PACKAGE_SPEC_ID: 7b4b394a2ac479c3d6d9b0aa24b944a3bb01e292 + - PACKAGE_SPEC_ID: 0101b0c84138c8e37a5affd1171d04237dcd044f steps: - setup_remote_docker: docker_layer_caching: false @@ -693,7 +768,7 @@ jobs: command: make -C packages*.lock write-package-cache-key name: Write package cache key - restore_cache: - key: package-7b4b394a2ac479c3d6d9b0aa24b944a3bb01e292-{{checksum ".buildcache/cache-keys/package-7b4b394a2ac479c3d6d9b0aa24b944a3bb01e292"}} + key: package-0101b0c84138c8e37a5affd1171d04237dcd044f-{{checksum ".buildcache/cache-keys/package-0101b0c84138c8e37a5affd1171d04237dcd044f"}} name: Restore package cache - run: command: |2 @@ -739,7 +814,7 @@ jobs: command: ls -lahR .buildcache/packages name: List packages - save_cache: - key: package-7b4b394a2ac479c3d6d9b0aa24b944a3bb01e292-{{checksum ".buildcache/cache-keys/package-7b4b394a2ac479c3d6d9b0aa24b944a3bb01e292"}} + key: package-0101b0c84138c8e37a5affd1171d04237dcd044f-{{checksum ".buildcache/cache-keys/package-0101b0c84138c8e37a5affd1171d04237dcd044f"}} name: Save package cache paths: - .buildcache/packages/store @@ -751,7 +826,7 @@ jobs: - AUTO_INSTALL_TOOLS: 'YES' - BUILDKIT_PROGRESS: plain - PRODUCT_REVISION: '' - - PACKAGE_SPEC_ID: db956b3de630063891714da55d71c00aee11a868 + - PACKAGE_SPEC_ID: 997f9a9e448c02fe17505a5b75635b9dd1104d92 steps: - setup_remote_docker: docker_layer_caching: false @@ -765,7 +840,7 @@ jobs: command: make -C packages*.lock write-package-cache-key name: Write package cache key - restore_cache: - key: package-db956b3de630063891714da55d71c00aee11a868-{{checksum ".buildcache/cache-keys/package-db956b3de630063891714da55d71c00aee11a868"}} + key: package-997f9a9e448c02fe17505a5b75635b9dd1104d92-{{checksum ".buildcache/cache-keys/package-997f9a9e448c02fe17505a5b75635b9dd1104d92"}} name: Restore package cache - run: command: |2 @@ -811,7 +886,7 @@ jobs: command: ls -lahR .buildcache/packages name: List packages - save_cache: - key: package-db956b3de630063891714da55d71c00aee11a868-{{checksum ".buildcache/cache-keys/package-db956b3de630063891714da55d71c00aee11a868"}} + key: package-997f9a9e448c02fe17505a5b75635b9dd1104d92-{{checksum ".buildcache/cache-keys/package-997f9a9e448c02fe17505a5b75635b9dd1104d92"}} name: Save package cache paths: - .buildcache/packages/store @@ -823,7 +898,7 @@ jobs: - AUTO_INSTALL_TOOLS: 'YES' - BUILDKIT_PROGRESS: plain - PRODUCT_REVISION: '' - - PACKAGE_SPEC_ID: 219d6c3eddb2481810202dccfc71a8467e4a077a + - PACKAGE_SPEC_ID: b04f52247addbb91ec89a35be1ddff9feadf7090 steps: - setup_remote_docker: docker_layer_caching: false @@ -837,7 +912,7 @@ jobs: command: make -C packages*.lock write-package-cache-key name: Write package cache key - restore_cache: - key: package-219d6c3eddb2481810202dccfc71a8467e4a077a-{{checksum ".buildcache/cache-keys/package-219d6c3eddb2481810202dccfc71a8467e4a077a"}} + key: package-b04f52247addbb91ec89a35be1ddff9feadf7090-{{checksum ".buildcache/cache-keys/package-b04f52247addbb91ec89a35be1ddff9feadf7090"}} name: Restore package cache - run: command: |2 @@ -883,7 +958,7 @@ jobs: command: ls -lahR .buildcache/packages name: List packages - save_cache: - key: package-219d6c3eddb2481810202dccfc71a8467e4a077a-{{checksum ".buildcache/cache-keys/package-219d6c3eddb2481810202dccfc71a8467e4a077a"}} + key: package-b04f52247addbb91ec89a35be1ddff9feadf7090-{{checksum ".buildcache/cache-keys/package-b04f52247addbb91ec89a35be1ddff9feadf7090"}} name: Save package cache paths: - .buildcache/packages/store @@ -906,7 +981,7 @@ jobs: - AUTO_INSTALL_TOOLS: 'YES' - BUILDKIT_PROGRESS: plain - PRODUCT_REVISION: '' - - PACKAGE_SPEC_ID: 02bf7eae9ab7517d616b442655b0db880a355a5e + - PACKAGE_SPEC_ID: b7d03bef8d16c702d595798df0cbda643915292c steps: - setup_remote_docker: docker_layer_caching: false @@ -920,7 +995,7 @@ jobs: command: make -C packages*.lock write-package-cache-key name: Write package cache key - restore_cache: - key: package-02bf7eae9ab7517d616b442655b0db880a355a5e-{{checksum ".buildcache/cache-keys/package-02bf7eae9ab7517d616b442655b0db880a355a5e"}} + key: package-b7d03bef8d16c702d595798df0cbda643915292c-{{checksum ".buildcache/cache-keys/package-b7d03bef8d16c702d595798df0cbda643915292c"}} name: Restore package cache - run: command: |2 @@ -966,7 +1041,7 @@ jobs: command: ls -lahR .buildcache/packages name: List packages - save_cache: - key: package-02bf7eae9ab7517d616b442655b0db880a355a5e-{{checksum ".buildcache/cache-keys/package-02bf7eae9ab7517d616b442655b0db880a355a5e"}} + key: package-b7d03bef8d16c702d595798df0cbda643915292c-{{checksum ".buildcache/cache-keys/package-b7d03bef8d16c702d595798df0cbda643915292c"}} name: Save package cache paths: - .buildcache/packages/store @@ -1128,7 +1203,7 @@ jobs: - AUTO_INSTALL_TOOLS: 'YES' - BUILDKIT_PROGRESS: plain - PRODUCT_REVISION: '' - - PACKAGE_SPEC_ID: f6ec6dce632eda1d6b6fc812702ddbfc78b00209 + - PACKAGE_SPEC_ID: 8dd295cf186df50e92ed66575b73e97003885428 steps: - setup_remote_docker: docker_layer_caching: false @@ -1142,7 +1217,7 @@ jobs: command: make -C packages*.lock write-package-cache-key name: Write package cache key - restore_cache: - key: package-f6ec6dce632eda1d6b6fc812702ddbfc78b00209-{{checksum ".buildcache/cache-keys/package-f6ec6dce632eda1d6b6fc812702ddbfc78b00209"}} + key: package-8dd295cf186df50e92ed66575b73e97003885428-{{checksum ".buildcache/cache-keys/package-8dd295cf186df50e92ed66575b73e97003885428"}} name: Restore package cache - run: command: |2 @@ -1188,7 +1263,7 @@ jobs: command: ls -lahR .buildcache/packages name: List packages - save_cache: - key: package-f6ec6dce632eda1d6b6fc812702ddbfc78b00209-{{checksum ".buildcache/cache-keys/package-f6ec6dce632eda1d6b6fc812702ddbfc78b00209"}} + key: package-8dd295cf186df50e92ed66575b73e97003885428-{{checksum ".buildcache/cache-keys/package-8dd295cf186df50e92ed66575b73e97003885428"}} name: Save package cache paths: - .buildcache/packages/store @@ -1211,7 +1286,7 @@ jobs: - AUTO_INSTALL_TOOLS: 'YES' - BUILDKIT_PROGRESS: plain - PRODUCT_REVISION: '' - - PACKAGE_SPEC_ID: 1c616d887a6ba6f72bf57aaae02b62cfe2260056 + - PACKAGE_SPEC_ID: 505ab0aca6b881919f05bb8e0125783a5799b4fc steps: - setup_remote_docker: docker_layer_caching: false @@ -1225,7 +1300,7 @@ jobs: command: make -C packages*.lock write-package-cache-key name: Write package cache key - restore_cache: - key: package-1c616d887a6ba6f72bf57aaae02b62cfe2260056-{{checksum ".buildcache/cache-keys/package-1c616d887a6ba6f72bf57aaae02b62cfe2260056"}} + key: package-505ab0aca6b881919f05bb8e0125783a5799b4fc-{{checksum ".buildcache/cache-keys/package-505ab0aca6b881919f05bb8e0125783a5799b4fc"}} name: Restore package cache - run: command: |2 @@ -1271,7 +1346,7 @@ jobs: command: ls -lahR .buildcache/packages name: List packages - save_cache: - key: package-1c616d887a6ba6f72bf57aaae02b62cfe2260056-{{checksum ".buildcache/cache-keys/package-1c616d887a6ba6f72bf57aaae02b62cfe2260056"}} + key: package-505ab0aca6b881919f05bb8e0125783a5799b4fc-{{checksum ".buildcache/cache-keys/package-505ab0aca6b881919f05bb8e0125783a5799b4fc"}} name: Save package cache paths: - .buildcache/packages/store @@ -1283,7 +1358,7 @@ jobs: - AUTO_INSTALL_TOOLS: 'YES' - BUILDKIT_PROGRESS: plain - PRODUCT_REVISION: '' - - PACKAGE_SPEC_ID: 0ab42b4e27fc1be0012f56be6566d1f79416efc4 + - PACKAGE_SPEC_ID: f56a0ca98b1bf7e4f647cedee7db20d8b940a1e0 steps: - setup_remote_docker: docker_layer_caching: false @@ -1297,7 +1372,7 @@ jobs: command: make -C packages*.lock write-package-cache-key name: Write package cache key - restore_cache: - key: package-0ab42b4e27fc1be0012f56be6566d1f79416efc4-{{checksum ".buildcache/cache-keys/package-0ab42b4e27fc1be0012f56be6566d1f79416efc4"}} + key: package-f56a0ca98b1bf7e4f647cedee7db20d8b940a1e0-{{checksum ".buildcache/cache-keys/package-f56a0ca98b1bf7e4f647cedee7db20d8b940a1e0"}} name: Restore package cache - run: command: |2 @@ -1343,7 +1418,7 @@ jobs: command: ls -lahR .buildcache/packages name: List packages - save_cache: - key: package-0ab42b4e27fc1be0012f56be6566d1f79416efc4-{{checksum ".buildcache/cache-keys/package-0ab42b4e27fc1be0012f56be6566d1f79416efc4"}} + key: package-f56a0ca98b1bf7e4f647cedee7db20d8b940a1e0-{{checksum ".buildcache/cache-keys/package-f56a0ca98b1bf7e4f647cedee7db20d8b940a1e0"}} name: Save package cache paths: - .buildcache/packages/store @@ -1355,7 +1430,7 @@ jobs: - AUTO_INSTALL_TOOLS: 'YES' - BUILDKIT_PROGRESS: plain - PRODUCT_REVISION: '' - - PACKAGE_SPEC_ID: 6f96cd412f008849de6529e990e5b46f75085f0e + - PACKAGE_SPEC_ID: 3735bcca8df7adfefa7d79ae87bb6731ed5e074f steps: - setup_remote_docker: docker_layer_caching: false @@ -1369,7 +1444,7 @@ jobs: command: make -C packages*.lock write-package-cache-key name: Write package cache key - restore_cache: - key: package-6f96cd412f008849de6529e990e5b46f75085f0e-{{checksum ".buildcache/cache-keys/package-6f96cd412f008849de6529e990e5b46f75085f0e"}} + key: package-3735bcca8df7adfefa7d79ae87bb6731ed5e074f-{{checksum ".buildcache/cache-keys/package-3735bcca8df7adfefa7d79ae87bb6731ed5e074f"}} name: Restore package cache - run: command: |2 @@ -1415,7 +1490,7 @@ jobs: command: ls -lahR .buildcache/packages name: List packages - save_cache: - key: package-6f96cd412f008849de6529e990e5b46f75085f0e-{{checksum ".buildcache/cache-keys/package-6f96cd412f008849de6529e990e5b46f75085f0e"}} + key: package-3735bcca8df7adfefa7d79ae87bb6731ed5e074f-{{checksum ".buildcache/cache-keys/package-3735bcca8df7adfefa7d79ae87bb6731ed5e074f"}} name: Save package cache paths: - .buildcache/packages/store @@ -1427,7 +1502,7 @@ jobs: - AUTO_INSTALL_TOOLS: 'YES' - BUILDKIT_PROGRESS: plain - PRODUCT_REVISION: '' - - PACKAGE_SPEC_ID: 60c90459d2d4978488819b2ca4118ba602308095 + - PACKAGE_SPEC_ID: 89694dc28c6ddc1d0648b69cefdcc9f67d59a240 steps: - setup_remote_docker: docker_layer_caching: false @@ -1441,7 +1516,7 @@ jobs: command: make -C packages*.lock write-package-cache-key name: Write package cache key - restore_cache: - key: package-60c90459d2d4978488819b2ca4118ba602308095-{{checksum ".buildcache/cache-keys/package-60c90459d2d4978488819b2ca4118ba602308095"}} + key: package-89694dc28c6ddc1d0648b69cefdcc9f67d59a240-{{checksum ".buildcache/cache-keys/package-89694dc28c6ddc1d0648b69cefdcc9f67d59a240"}} name: Restore package cache - run: command: |2 @@ -1487,7 +1562,7 @@ jobs: command: ls -lahR .buildcache/packages name: List packages - save_cache: - key: package-60c90459d2d4978488819b2ca4118ba602308095-{{checksum ".buildcache/cache-keys/package-60c90459d2d4978488819b2ca4118ba602308095"}} + key: package-89694dc28c6ddc1d0648b69cefdcc9f67d59a240-{{checksum ".buildcache/cache-keys/package-89694dc28c6ddc1d0648b69cefdcc9f67d59a240"}} name: Save package cache paths: - .buildcache/packages/store @@ -1503,7 +1578,7 @@ jobs: make test-sql POSTGRES_DOCKER_IMAGE_BASE=docker.mirror.hashicorp.services/postgres PG_DOCKER_TAG=latest name: Run SQL PgTap Tests workflows: - build-8551e55f6857ec68: + build-7f1510c592a3019a: jobs: - build-common-layers - darwin_amd64_package: @@ -1512,6 +1587,7 @@ workflows: - bundle-releases: requires: - darwin_amd64_package + - darwin_arm64_package - freebsd_386_package - freebsd_amd64_package - freebsd_arm_package @@ -1526,6 +1602,9 @@ workflows: - solaris_amd64_package - windows_386_package - windows_amd64_package + - darwin_arm64_package: + requires: + - build-common-layers - freebsd_386_package: requires: - build-common-layers diff --git a/.circleci/config/@build-release.yml b/.circleci/config/@build-release.yml index e615911e1..f3fa3b482 100644 --- a/.circleci/config/@build-release.yml +++ b/.circleci/config/@build-release.yml @@ -136,49 +136,52 @@ jobs: command: mkdir -p .buildcache && echo "*" > .buildcache/.gitignore name: Ignore .buildcache - restore_cache: - key: package-d31a4f1f3856b35a8a0c1dd5eb6a34f62026530f-{{checksum ".buildcache/cache-keys/package-d31a4f1f3856b35a8a0c1dd5eb6a34f62026530f"}} + key: package-5e182eecadd802ce4fde19b40733c21f1920d680-{{checksum ".buildcache/cache-keys/package-5e182eecadd802ce4fde19b40733c21f1920d680"}} name: Restore package cache - restore_cache: - key: package-a315309198e0c87a3964c4a0514206e1a1601de5-{{checksum ".buildcache/cache-keys/package-a315309198e0c87a3964c4a0514206e1a1601de5"}} + key: package-72ea9453ed75bfab19ee6a1fd34e7ac751e5e71d-{{checksum ".buildcache/cache-keys/package-72ea9453ed75bfab19ee6a1fd34e7ac751e5e71d"}} name: Restore package cache - restore_cache: - key: package-7b4b394a2ac479c3d6d9b0aa24b944a3bb01e292-{{checksum ".buildcache/cache-keys/package-7b4b394a2ac479c3d6d9b0aa24b944a3bb01e292"}} + key: package-ccae35a6ffa3283396cbe5608aa626a65cf951f4-{{checksum ".buildcache/cache-keys/package-ccae35a6ffa3283396cbe5608aa626a65cf951f4"}} name: Restore package cache - restore_cache: - key: package-6f96cd412f008849de6529e990e5b46f75085f0e-{{checksum ".buildcache/cache-keys/package-6f96cd412f008849de6529e990e5b46f75085f0e"}} + key: package-0101b0c84138c8e37a5affd1171d04237dcd044f-{{checksum ".buildcache/cache-keys/package-0101b0c84138c8e37a5affd1171d04237dcd044f"}} name: Restore package cache - restore_cache: - key: package-36979f31c13248a027ff8866979f2aa282c9f9d6-{{checksum ".buildcache/cache-keys/package-36979f31c13248a027ff8866979f2aa282c9f9d6"}} + key: package-3735bcca8df7adfefa7d79ae87bb6731ed5e074f-{{checksum ".buildcache/cache-keys/package-3735bcca8df7adfefa7d79ae87bb6731ed5e074f"}} name: Restore package cache - restore_cache: - key: package-219d6c3eddb2481810202dccfc71a8467e4a077a-{{checksum ".buildcache/cache-keys/package-219d6c3eddb2481810202dccfc71a8467e4a077a"}} + key: package-81b9112a9e1bd1d33778c4673adbec942c595a7e-{{checksum ".buildcache/cache-keys/package-81b9112a9e1bd1d33778c4673adbec942c595a7e"}} name: Restore package cache - restore_cache: - key: package-1a1ebfab53158923bc4779535ebd667ff8ed5ca3-{{checksum ".buildcache/cache-keys/package-1a1ebfab53158923bc4779535ebd667ff8ed5ca3"}} + key: package-b04f52247addbb91ec89a35be1ddff9feadf7090-{{checksum ".buildcache/cache-keys/package-b04f52247addbb91ec89a35be1ddff9feadf7090"}} name: Restore package cache - restore_cache: - key: package-100f99d19a296ddc4ba797c583b4c68266d61c52-{{checksum ".buildcache/cache-keys/package-100f99d19a296ddc4ba797c583b4c68266d61c52"}} + key: package-25033317f5db33c76740dba3e792063240c317b1-{{checksum ".buildcache/cache-keys/package-25033317f5db33c76740dba3e792063240c317b1"}} name: Restore package cache - restore_cache: - key: package-0ab42b4e27fc1be0012f56be6566d1f79416efc4-{{checksum ".buildcache/cache-keys/package-0ab42b4e27fc1be0012f56be6566d1f79416efc4"}} + key: package-dc42bacf89f0d78468257a40c404e141fa9272c8-{{checksum ".buildcache/cache-keys/package-dc42bacf89f0d78468257a40c404e141fa9272c8"}} name: Restore package cache - restore_cache: - key: package-f6ec6dce632eda1d6b6fc812702ddbfc78b00209-{{checksum ".buildcache/cache-keys/package-f6ec6dce632eda1d6b6fc812702ddbfc78b00209"}} + key: package-f56a0ca98b1bf7e4f647cedee7db20d8b940a1e0-{{checksum ".buildcache/cache-keys/package-f56a0ca98b1bf7e4f647cedee7db20d8b940a1e0"}} name: Restore package cache - restore_cache: - key: package-db956b3de630063891714da55d71c00aee11a868-{{checksum ".buildcache/cache-keys/package-db956b3de630063891714da55d71c00aee11a868"}} + key: package-8dd295cf186df50e92ed66575b73e97003885428-{{checksum ".buildcache/cache-keys/package-8dd295cf186df50e92ed66575b73e97003885428"}} name: Restore package cache - restore_cache: - key: package-1c616d887a6ba6f72bf57aaae02b62cfe2260056-{{checksum ".buildcache/cache-keys/package-1c616d887a6ba6f72bf57aaae02b62cfe2260056"}} + key: package-997f9a9e448c02fe17505a5b75635b9dd1104d92-{{checksum ".buildcache/cache-keys/package-997f9a9e448c02fe17505a5b75635b9dd1104d92"}} name: Restore package cache - restore_cache: - key: package-02bf7eae9ab7517d616b442655b0db880a355a5e-{{checksum ".buildcache/cache-keys/package-02bf7eae9ab7517d616b442655b0db880a355a5e"}} + key: package-505ab0aca6b881919f05bb8e0125783a5799b4fc-{{checksum ".buildcache/cache-keys/package-505ab0aca6b881919f05bb8e0125783a5799b4fc"}} name: Restore package cache - restore_cache: - key: package-e57134ec78767637844ee933e9c9a58bd3a24182-{{checksum ".buildcache/cache-keys/package-e57134ec78767637844ee933e9c9a58bd3a24182"}} + key: package-b7d03bef8d16c702d595798df0cbda643915292c-{{checksum ".buildcache/cache-keys/package-b7d03bef8d16c702d595798df0cbda643915292c"}} name: Restore package cache - restore_cache: - key: package-60c90459d2d4978488819b2ca4118ba602308095-{{checksum ".buildcache/cache-keys/package-60c90459d2d4978488819b2ca4118ba602308095"}} + key: package-a68848e3db3953d016f73aabb3d732a0333b7bc9-{{checksum ".buildcache/cache-keys/package-a68848e3db3953d016f73aabb3d732a0333b7bc9"}} + name: Restore package cache + - restore_cache: + key: package-89694dc28c6ddc1d0648b69cefdcc9f67d59a240-{{checksum ".buildcache/cache-keys/package-89694dc28c6ddc1d0648b69cefdcc9f67d59a240"}} name: Restore package cache - run: command: make package-meta-all @@ -190,20 +193,20 @@ jobs: command: ls -lahR .buildcache name: List Build Cache - run: - command: cp packages*.lock/pkgs.yml lockfile-8551e55f6857ec68.yml + command: cp packages*.lock/pkgs.yml lockfile-7f1510c592a3019a.yml name: Update Lockfile Name - run: - command: tar -czf packages-8551e55f6857ec68.tar.gz .buildcache/packages lockfile-8551e55f6857ec68.yml + command: tar -czf packages-7f1510c592a3019a.tar.gz .buildcache/packages lockfile-7f1510c592a3019a.yml name: Create Raw Package Tarball - run: - command: tar -czf meta-8551e55f6857ec68.tar.gz .buildcache/packages/store/*.json lockfile-8551e55f6857ec68.yml + command: tar -czf meta-7f1510c592a3019a.tar.gz .buildcache/packages/store/*.json lockfile-7f1510c592a3019a.yml name: Create Metadata Tarball - store_artifacts: - path: lockfile-8551e55f6857ec68.yml + path: lockfile-7f1510c592a3019a.yml - store_artifacts: - path: packages-8551e55f6857ec68.tar.gz + path: packages-7f1510c592a3019a.tar.gz - store_artifacts: - path: meta-8551e55f6857ec68.tar.gz + path: meta-7f1510c592a3019a.tar.gz - store_artifacts: path: .buildcache/packages darwin_amd64_package: @@ -221,7 +224,7 @@ jobs: command: make -C packages*.lock write-package-cache-key name: Write package cache key - restore_cache: - key: package-d31a4f1f3856b35a8a0c1dd5eb6a34f62026530f-{{checksum ".buildcache/cache-keys/package-d31a4f1f3856b35a8a0c1dd5eb6a34f62026530f"}} + key: package-5e182eecadd802ce4fde19b40733c21f1920d680-{{checksum ".buildcache/cache-keys/package-5e182eecadd802ce4fde19b40733c21f1920d680"}} name: Restore package cache - run: command: |2 @@ -269,10 +272,77 @@ jobs: - save_cache: paths: - .buildcache/packages/store - key: package-d31a4f1f3856b35a8a0c1dd5eb6a34f62026530f-{{checksum ".buildcache/cache-keys/package-d31a4f1f3856b35a8a0c1dd5eb6a34f62026530f"}} + key: package-5e182eecadd802ce4fde19b40733c21f1920d680-{{checksum ".buildcache/cache-keys/package-5e182eecadd802ce4fde19b40733c21f1920d680"}} name: Save package cache environment: - PACKAGE_SPEC_ID: d31a4f1f3856b35a8a0c1dd5eb6a34f62026530f + PACKAGE_SPEC_ID: 5e182eecadd802ce4fde19b40733c21f1920d680 + darwin_arm64_package: + executor: builder + steps: + - setup_remote_docker: + version: 19.03.12 + docker_layer_caching: false + - add_ssh_keys: + fingerprints: + - 0e:03:77:f4:e2:c3:56:c2:53:6a:03:e1:31:91:2f:06 + - checkout: + path: . + - run: + command: make -C packages*.lock write-package-cache-key + name: Write package cache key + - restore_cache: + key: package-72ea9453ed75bfab19ee6a1fd34e7ac751e5e71d-{{checksum ".buildcache/cache-keys/package-72ea9453ed75bfab19ee6a1fd34e7ac751e5e71d"}} + name: Restore package cache + - run: + command: |2 + + if ! { PKG=$(find .buildcache/packages/store -maxdepth 1 -mindepth 1 -name '*.zip' 2> /dev/null) && [ -n "$PKG" ]; }; then + echo "No package found, continuing with build." + exit 0 + fi + echo "Package already cached, skipping build." + circleci-agent step halt + name: Check cache status + - run: + command: make -C packages*.lock write-builder-cache-keys + name: Write builder layer cache keys + - restore_cache: + key: copy-source_6be6_{{checksum ".buildcache/cache-keys/copy-source-6be6bbcf469c2d26620194eeee87f0c4ba470bde"}} + keys: + - go-modules_74c0_{{checksum ".buildcache/cache-keys/go-modules-74c0c94188690e22d738cb1784f8feba7c3dc00d"}} + - ui_dfc9_{{checksum ".buildcache/cache-keys/ui-dfc937906b9b645655c44bc782ef13c64ebe8dbb"}} + - base_cc87_{{checksum ".buildcache/cache-keys/base-cc87158f920a83b1c241d68474eed6734b32c511"}} + name: 'Restore Builder Image Cache: copy-source' + - run: + command: make -C packages*.lock load-builder-cache + name: Load whatever builder cache we have (if any) into the Docker daemon + no_output_timeout: 30m + - run: + command: |2- + + KEYFILE="$HOME/.ssh/id_rsa_c6969882dc046c39ddac8305e3151c98" + eval "$(ssh-agent -s)" + if [ -f "$KEYFILE" ]; then + ssh-add "$KEYFILE" + else + echo "==> INFO: SSH key for github.com not found" + echo " Attempts to access private repositories from within" + echo " the build will fail, e.g. for private go modules, or" + echo " attempts to directly clone private repositories." + fi + + make -C packages*.lock package + name: Compile Package + - run: + command: ls -lahR .buildcache/packages + name: List packages + - save_cache: + paths: + - .buildcache/packages/store + key: package-72ea9453ed75bfab19ee6a1fd34e7ac751e5e71d-{{checksum ".buildcache/cache-keys/package-72ea9453ed75bfab19ee6a1fd34e7ac751e5e71d"}} + name: Save package cache + environment: + PACKAGE_SPEC_ID: 72ea9453ed75bfab19ee6a1fd34e7ac751e5e71d freebsd_386_package: executor: builder steps: @@ -288,7 +358,7 @@ jobs: command: make -C packages*.lock write-package-cache-key name: Write package cache key - restore_cache: - key: package-a315309198e0c87a3964c4a0514206e1a1601de5-{{checksum ".buildcache/cache-keys/package-a315309198e0c87a3964c4a0514206e1a1601de5"}} + key: package-ccae35a6ffa3283396cbe5608aa626a65cf951f4-{{checksum ".buildcache/cache-keys/package-ccae35a6ffa3283396cbe5608aa626a65cf951f4"}} name: Restore package cache - run: command: |2 @@ -336,10 +406,10 @@ jobs: - save_cache: paths: - .buildcache/packages/store - key: package-a315309198e0c87a3964c4a0514206e1a1601de5-{{checksum ".buildcache/cache-keys/package-a315309198e0c87a3964c4a0514206e1a1601de5"}} + key: package-ccae35a6ffa3283396cbe5608aa626a65cf951f4-{{checksum ".buildcache/cache-keys/package-ccae35a6ffa3283396cbe5608aa626a65cf951f4"}} name: Save package cache environment: - PACKAGE_SPEC_ID: a315309198e0c87a3964c4a0514206e1a1601de5 + PACKAGE_SPEC_ID: ccae35a6ffa3283396cbe5608aa626a65cf951f4 freebsd_amd64_package: executor: builder steps: @@ -355,7 +425,7 @@ jobs: command: make -C packages*.lock write-package-cache-key name: Write package cache key - restore_cache: - key: package-7b4b394a2ac479c3d6d9b0aa24b944a3bb01e292-{{checksum ".buildcache/cache-keys/package-7b4b394a2ac479c3d6d9b0aa24b944a3bb01e292"}} + key: package-0101b0c84138c8e37a5affd1171d04237dcd044f-{{checksum ".buildcache/cache-keys/package-0101b0c84138c8e37a5affd1171d04237dcd044f"}} name: Restore package cache - run: command: |2 @@ -403,10 +473,10 @@ jobs: - save_cache: paths: - .buildcache/packages/store - key: package-7b4b394a2ac479c3d6d9b0aa24b944a3bb01e292-{{checksum ".buildcache/cache-keys/package-7b4b394a2ac479c3d6d9b0aa24b944a3bb01e292"}} + key: package-0101b0c84138c8e37a5affd1171d04237dcd044f-{{checksum ".buildcache/cache-keys/package-0101b0c84138c8e37a5affd1171d04237dcd044f"}} name: Save package cache environment: - PACKAGE_SPEC_ID: 7b4b394a2ac479c3d6d9b0aa24b944a3bb01e292 + PACKAGE_SPEC_ID: 0101b0c84138c8e37a5affd1171d04237dcd044f freebsd_arm_package: executor: builder steps: @@ -422,7 +492,7 @@ jobs: command: make -C packages*.lock write-package-cache-key name: Write package cache key - restore_cache: - key: package-6f96cd412f008849de6529e990e5b46f75085f0e-{{checksum ".buildcache/cache-keys/package-6f96cd412f008849de6529e990e5b46f75085f0e"}} + key: package-3735bcca8df7adfefa7d79ae87bb6731ed5e074f-{{checksum ".buildcache/cache-keys/package-3735bcca8df7adfefa7d79ae87bb6731ed5e074f"}} name: Restore package cache - run: command: |2 @@ -470,10 +540,10 @@ jobs: - save_cache: paths: - .buildcache/packages/store - key: package-6f96cd412f008849de6529e990e5b46f75085f0e-{{checksum ".buildcache/cache-keys/package-6f96cd412f008849de6529e990e5b46f75085f0e"}} + key: package-3735bcca8df7adfefa7d79ae87bb6731ed5e074f-{{checksum ".buildcache/cache-keys/package-3735bcca8df7adfefa7d79ae87bb6731ed5e074f"}} name: Save package cache environment: - PACKAGE_SPEC_ID: 6f96cd412f008849de6529e990e5b46f75085f0e + PACKAGE_SPEC_ID: 3735bcca8df7adfefa7d79ae87bb6731ed5e074f linux_386_package: executor: builder steps: @@ -489,7 +559,7 @@ jobs: command: make -C packages*.lock write-package-cache-key name: Write package cache key - restore_cache: - key: package-36979f31c13248a027ff8866979f2aa282c9f9d6-{{checksum ".buildcache/cache-keys/package-36979f31c13248a027ff8866979f2aa282c9f9d6"}} + key: package-81b9112a9e1bd1d33778c4673adbec942c595a7e-{{checksum ".buildcache/cache-keys/package-81b9112a9e1bd1d33778c4673adbec942c595a7e"}} name: Restore package cache - run: command: |2 @@ -537,10 +607,10 @@ jobs: - save_cache: paths: - .buildcache/packages/store - key: package-36979f31c13248a027ff8866979f2aa282c9f9d6-{{checksum ".buildcache/cache-keys/package-36979f31c13248a027ff8866979f2aa282c9f9d6"}} + key: package-81b9112a9e1bd1d33778c4673adbec942c595a7e-{{checksum ".buildcache/cache-keys/package-81b9112a9e1bd1d33778c4673adbec942c595a7e"}} name: Save package cache environment: - PACKAGE_SPEC_ID: 36979f31c13248a027ff8866979f2aa282c9f9d6 + PACKAGE_SPEC_ID: 81b9112a9e1bd1d33778c4673adbec942c595a7e linux_amd64_package: executor: builder steps: @@ -556,7 +626,7 @@ jobs: command: make -C packages*.lock write-package-cache-key name: Write package cache key - restore_cache: - key: package-219d6c3eddb2481810202dccfc71a8467e4a077a-{{checksum ".buildcache/cache-keys/package-219d6c3eddb2481810202dccfc71a8467e4a077a"}} + key: package-b04f52247addbb91ec89a35be1ddff9feadf7090-{{checksum ".buildcache/cache-keys/package-b04f52247addbb91ec89a35be1ddff9feadf7090"}} name: Restore package cache - run: command: |2 @@ -604,10 +674,10 @@ jobs: - save_cache: paths: - .buildcache/packages/store - key: package-219d6c3eddb2481810202dccfc71a8467e4a077a-{{checksum ".buildcache/cache-keys/package-219d6c3eddb2481810202dccfc71a8467e4a077a"}} + key: package-b04f52247addbb91ec89a35be1ddff9feadf7090-{{checksum ".buildcache/cache-keys/package-b04f52247addbb91ec89a35be1ddff9feadf7090"}} name: Save package cache environment: - PACKAGE_SPEC_ID: 219d6c3eddb2481810202dccfc71a8467e4a077a + PACKAGE_SPEC_ID: b04f52247addbb91ec89a35be1ddff9feadf7090 linux_arm_package: executor: builder steps: @@ -623,7 +693,7 @@ jobs: command: make -C packages*.lock write-package-cache-key name: Write package cache key - restore_cache: - key: package-1a1ebfab53158923bc4779535ebd667ff8ed5ca3-{{checksum ".buildcache/cache-keys/package-1a1ebfab53158923bc4779535ebd667ff8ed5ca3"}} + key: package-25033317f5db33c76740dba3e792063240c317b1-{{checksum ".buildcache/cache-keys/package-25033317f5db33c76740dba3e792063240c317b1"}} name: Restore package cache - run: command: |2 @@ -671,10 +741,10 @@ jobs: - save_cache: paths: - .buildcache/packages/store - key: package-1a1ebfab53158923bc4779535ebd667ff8ed5ca3-{{checksum ".buildcache/cache-keys/package-1a1ebfab53158923bc4779535ebd667ff8ed5ca3"}} + key: package-25033317f5db33c76740dba3e792063240c317b1-{{checksum ".buildcache/cache-keys/package-25033317f5db33c76740dba3e792063240c317b1"}} name: Save package cache environment: - PACKAGE_SPEC_ID: 1a1ebfab53158923bc4779535ebd667ff8ed5ca3 + PACKAGE_SPEC_ID: 25033317f5db33c76740dba3e792063240c317b1 linux_arm64_package: executor: builder steps: @@ -690,7 +760,7 @@ jobs: command: make -C packages*.lock write-package-cache-key name: Write package cache key - restore_cache: - key: package-100f99d19a296ddc4ba797c583b4c68266d61c52-{{checksum ".buildcache/cache-keys/package-100f99d19a296ddc4ba797c583b4c68266d61c52"}} + key: package-dc42bacf89f0d78468257a40c404e141fa9272c8-{{checksum ".buildcache/cache-keys/package-dc42bacf89f0d78468257a40c404e141fa9272c8"}} name: Restore package cache - run: command: |2 @@ -738,10 +808,10 @@ jobs: - save_cache: paths: - .buildcache/packages/store - key: package-100f99d19a296ddc4ba797c583b4c68266d61c52-{{checksum ".buildcache/cache-keys/package-100f99d19a296ddc4ba797c583b4c68266d61c52"}} + key: package-dc42bacf89f0d78468257a40c404e141fa9272c8-{{checksum ".buildcache/cache-keys/package-dc42bacf89f0d78468257a40c404e141fa9272c8"}} name: Save package cache environment: - PACKAGE_SPEC_ID: 100f99d19a296ddc4ba797c583b4c68266d61c52 + PACKAGE_SPEC_ID: dc42bacf89f0d78468257a40c404e141fa9272c8 netbsd_386_package: executor: builder steps: @@ -757,7 +827,7 @@ jobs: command: make -C packages*.lock write-package-cache-key name: Write package cache key - restore_cache: - key: package-0ab42b4e27fc1be0012f56be6566d1f79416efc4-{{checksum ".buildcache/cache-keys/package-0ab42b4e27fc1be0012f56be6566d1f79416efc4"}} + key: package-f56a0ca98b1bf7e4f647cedee7db20d8b940a1e0-{{checksum ".buildcache/cache-keys/package-f56a0ca98b1bf7e4f647cedee7db20d8b940a1e0"}} name: Restore package cache - run: command: |2 @@ -805,10 +875,10 @@ jobs: - save_cache: paths: - .buildcache/packages/store - key: package-0ab42b4e27fc1be0012f56be6566d1f79416efc4-{{checksum ".buildcache/cache-keys/package-0ab42b4e27fc1be0012f56be6566d1f79416efc4"}} + key: package-f56a0ca98b1bf7e4f647cedee7db20d8b940a1e0-{{checksum ".buildcache/cache-keys/package-f56a0ca98b1bf7e4f647cedee7db20d8b940a1e0"}} name: Save package cache environment: - PACKAGE_SPEC_ID: 0ab42b4e27fc1be0012f56be6566d1f79416efc4 + PACKAGE_SPEC_ID: f56a0ca98b1bf7e4f647cedee7db20d8b940a1e0 netbsd_amd64_package: executor: builder steps: @@ -824,7 +894,7 @@ jobs: command: make -C packages*.lock write-package-cache-key name: Write package cache key - restore_cache: - key: package-f6ec6dce632eda1d6b6fc812702ddbfc78b00209-{{checksum ".buildcache/cache-keys/package-f6ec6dce632eda1d6b6fc812702ddbfc78b00209"}} + key: package-8dd295cf186df50e92ed66575b73e97003885428-{{checksum ".buildcache/cache-keys/package-8dd295cf186df50e92ed66575b73e97003885428"}} name: Restore package cache - run: command: |2 @@ -872,10 +942,10 @@ jobs: - save_cache: paths: - .buildcache/packages/store - key: package-f6ec6dce632eda1d6b6fc812702ddbfc78b00209-{{checksum ".buildcache/cache-keys/package-f6ec6dce632eda1d6b6fc812702ddbfc78b00209"}} + key: package-8dd295cf186df50e92ed66575b73e97003885428-{{checksum ".buildcache/cache-keys/package-8dd295cf186df50e92ed66575b73e97003885428"}} name: Save package cache environment: - PACKAGE_SPEC_ID: f6ec6dce632eda1d6b6fc812702ddbfc78b00209 + PACKAGE_SPEC_ID: 8dd295cf186df50e92ed66575b73e97003885428 openbsd_386_package: executor: builder steps: @@ -891,7 +961,7 @@ jobs: command: make -C packages*.lock write-package-cache-key name: Write package cache key - restore_cache: - key: package-db956b3de630063891714da55d71c00aee11a868-{{checksum ".buildcache/cache-keys/package-db956b3de630063891714da55d71c00aee11a868"}} + key: package-997f9a9e448c02fe17505a5b75635b9dd1104d92-{{checksum ".buildcache/cache-keys/package-997f9a9e448c02fe17505a5b75635b9dd1104d92"}} name: Restore package cache - run: command: |2 @@ -939,10 +1009,10 @@ jobs: - save_cache: paths: - .buildcache/packages/store - key: package-db956b3de630063891714da55d71c00aee11a868-{{checksum ".buildcache/cache-keys/package-db956b3de630063891714da55d71c00aee11a868"}} + key: package-997f9a9e448c02fe17505a5b75635b9dd1104d92-{{checksum ".buildcache/cache-keys/package-997f9a9e448c02fe17505a5b75635b9dd1104d92"}} name: Save package cache environment: - PACKAGE_SPEC_ID: db956b3de630063891714da55d71c00aee11a868 + PACKAGE_SPEC_ID: 997f9a9e448c02fe17505a5b75635b9dd1104d92 openbsd_amd64_package: executor: builder steps: @@ -958,7 +1028,7 @@ jobs: command: make -C packages*.lock write-package-cache-key name: Write package cache key - restore_cache: - key: package-1c616d887a6ba6f72bf57aaae02b62cfe2260056-{{checksum ".buildcache/cache-keys/package-1c616d887a6ba6f72bf57aaae02b62cfe2260056"}} + key: package-505ab0aca6b881919f05bb8e0125783a5799b4fc-{{checksum ".buildcache/cache-keys/package-505ab0aca6b881919f05bb8e0125783a5799b4fc"}} name: Restore package cache - run: command: |2 @@ -1006,10 +1076,10 @@ jobs: - save_cache: paths: - .buildcache/packages/store - key: package-1c616d887a6ba6f72bf57aaae02b62cfe2260056-{{checksum ".buildcache/cache-keys/package-1c616d887a6ba6f72bf57aaae02b62cfe2260056"}} + key: package-505ab0aca6b881919f05bb8e0125783a5799b4fc-{{checksum ".buildcache/cache-keys/package-505ab0aca6b881919f05bb8e0125783a5799b4fc"}} name: Save package cache environment: - PACKAGE_SPEC_ID: 1c616d887a6ba6f72bf57aaae02b62cfe2260056 + PACKAGE_SPEC_ID: 505ab0aca6b881919f05bb8e0125783a5799b4fc solaris_amd64_package: executor: builder steps: @@ -1025,7 +1095,7 @@ jobs: command: make -C packages*.lock write-package-cache-key name: Write package cache key - restore_cache: - key: package-02bf7eae9ab7517d616b442655b0db880a355a5e-{{checksum ".buildcache/cache-keys/package-02bf7eae9ab7517d616b442655b0db880a355a5e"}} + key: package-b7d03bef8d16c702d595798df0cbda643915292c-{{checksum ".buildcache/cache-keys/package-b7d03bef8d16c702d595798df0cbda643915292c"}} name: Restore package cache - run: command: |2 @@ -1073,10 +1143,10 @@ jobs: - save_cache: paths: - .buildcache/packages/store - key: package-02bf7eae9ab7517d616b442655b0db880a355a5e-{{checksum ".buildcache/cache-keys/package-02bf7eae9ab7517d616b442655b0db880a355a5e"}} + key: package-b7d03bef8d16c702d595798df0cbda643915292c-{{checksum ".buildcache/cache-keys/package-b7d03bef8d16c702d595798df0cbda643915292c"}} name: Save package cache environment: - PACKAGE_SPEC_ID: 02bf7eae9ab7517d616b442655b0db880a355a5e + PACKAGE_SPEC_ID: b7d03bef8d16c702d595798df0cbda643915292c windows_386_package: executor: builder steps: @@ -1092,7 +1162,7 @@ jobs: command: make -C packages*.lock write-package-cache-key name: Write package cache key - restore_cache: - key: package-e57134ec78767637844ee933e9c9a58bd3a24182-{{checksum ".buildcache/cache-keys/package-e57134ec78767637844ee933e9c9a58bd3a24182"}} + key: package-a68848e3db3953d016f73aabb3d732a0333b7bc9-{{checksum ".buildcache/cache-keys/package-a68848e3db3953d016f73aabb3d732a0333b7bc9"}} name: Restore package cache - run: command: |2 @@ -1140,10 +1210,10 @@ jobs: - save_cache: paths: - .buildcache/packages/store - key: package-e57134ec78767637844ee933e9c9a58bd3a24182-{{checksum ".buildcache/cache-keys/package-e57134ec78767637844ee933e9c9a58bd3a24182"}} + key: package-a68848e3db3953d016f73aabb3d732a0333b7bc9-{{checksum ".buildcache/cache-keys/package-a68848e3db3953d016f73aabb3d732a0333b7bc9"}} name: Save package cache environment: - PACKAGE_SPEC_ID: e57134ec78767637844ee933e9c9a58bd3a24182 + PACKAGE_SPEC_ID: a68848e3db3953d016f73aabb3d732a0333b7bc9 windows_amd64_package: executor: builder steps: @@ -1159,7 +1229,7 @@ jobs: command: make -C packages*.lock write-package-cache-key name: Write package cache key - restore_cache: - key: package-60c90459d2d4978488819b2ca4118ba602308095-{{checksum ".buildcache/cache-keys/package-60c90459d2d4978488819b2ca4118ba602308095"}} + key: package-89694dc28c6ddc1d0648b69cefdcc9f67d59a240-{{checksum ".buildcache/cache-keys/package-89694dc28c6ddc1d0648b69cefdcc9f67d59a240"}} name: Restore package cache - run: command: |2 @@ -1207,12 +1277,12 @@ jobs: - save_cache: paths: - .buildcache/packages/store - key: package-60c90459d2d4978488819b2ca4118ba602308095-{{checksum ".buildcache/cache-keys/package-60c90459d2d4978488819b2ca4118ba602308095"}} + key: package-89694dc28c6ddc1d0648b69cefdcc9f67d59a240-{{checksum ".buildcache/cache-keys/package-89694dc28c6ddc1d0648b69cefdcc9f67d59a240"}} name: Save package cache environment: - PACKAGE_SPEC_ID: 60c90459d2d4978488819b2ca4118ba602308095 + PACKAGE_SPEC_ID: 89694dc28c6ddc1d0648b69cefdcc9f67d59a240 workflows: - build-8551e55f6857ec68: + build-7f1510c592a3019a: jobs: - build-common-layers: {} - darwin_amd64_package: @@ -1221,6 +1291,7 @@ workflows: - bundle-releases: requires: - darwin_amd64_package + - darwin_arm64_package - freebsd_386_package - freebsd_amd64_package - freebsd_arm_package @@ -1235,6 +1306,9 @@ workflows: - solaris_amd64_package - windows_386_package - windows_amd64_package + - darwin_arm64_package: + requires: + - build-common-layers - freebsd_386_package: requires: - build-common-layers diff --git a/.gitignore b/.gitignore index 23b0ea13e..5e180a751 100644 --- a/.gitignore +++ b/.gitignore @@ -120,6 +120,7 @@ main /bin/ update-ui-assets* /plugins/kms/assets/ +/plugins/host/assets/ # Test config file test*.hcl diff --git a/Makefile b/Makefile index 5ea33563f..448e3a8cc 100644 --- a/Makefile +++ b/Makefile @@ -28,11 +28,6 @@ dev: BUILD_TAGS+=dev dev: BUILD_TAGS+=ui dev: build-ui-ifne @echo "==> Building Boundary with dev and UI features enabled" - @CGO_ENABLED=$(CGO_ENABLED) BUILD_TAGS='$(BUILD_TAGS)' BOUNDARY_DEV_BUILD=1 sh -c "'$(CURDIR)/scripts/build.sh'" - -bin: BUILD_TAGS+=ui -bin: build-ui - @echo "==> Building Boundary with UI features enabled" @CGO_ENABLED=$(CGO_ENABLED) BUILD_TAGS='$(BUILD_TAGS)' sh -c "'$(CURDIR)/scripts/build.sh'" fmt: @@ -118,6 +113,9 @@ protobuild: @protoc-go-inject-tag -input=./internal/db/db_test/db_test.pb.go @protoc-go-inject-tag -input=./internal/host/store/host.pb.go @protoc-go-inject-tag -input=./internal/host/static/store/static.pb.go + @protoc-go-inject-tag -input=./internal/host/plugin/store/host.pb.go + @protoc-go-inject-tag -input=./internal/plugin/host/store/plugin.pb.go + @protoc-go-inject-tag -input=./internal/plugin/store/plugin.pb.go @protoc-go-inject-tag -input=./internal/authtoken/store/authtoken.pb.go @protoc-go-inject-tag -input=./internal/auth/store/account.pb.go @protoc-go-inject-tag -input=./internal/auth/password/store/password.pb.go @@ -172,6 +170,9 @@ test-database-down: make -C testing/dbtest/docker clean test-ci: install-go +test-ci: export CI_BUILD=1 +test-ci: + CGO_ENABLED=$(CGO_ENABLED) BUILD_TAGS='$(BUILD_TAGS)' sh -c "'$(CURDIR)/scripts/build.sh'" ~/.go/bin/go test ./... -v $(TESTARGS) -timeout 120m test-sql: diff --git a/README.md b/README.md index f2685151e..d65617018 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ for permanent installations, are detailed in our [Installation Guide](https://ww ### Build and Start Boundary in Dev Mode If you have the following requirements met locally: -- Golang v1.16 or greater +- Golang v1.17 or greater - Docker - [Boundary UI Dependencies](https://github.com/hashicorp/boundary-ui#prerequisites) diff --git a/api/go.mod b/api/go.mod index 83b9febd2..136a7e302 100644 --- a/api/go.mod +++ b/api/go.mod @@ -1,6 +1,6 @@ module github.com/hashicorp/boundary/api -go 1.15 +go 1.17 require ( github.com/fatih/structs v1.1.0 @@ -12,8 +12,25 @@ require ( github.com/hashicorp/go-uuid v1.0.2 github.com/mr-tron/base58 v1.2.0 github.com/stretchr/testify v1.7.0 - golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e google.golang.org/grpc v1.35.0 google.golang.org/protobuf v1.27.1 ) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fatih/color v1.7.0 // indirect + github.com/golang/protobuf v1.5.0 // indirect + github.com/hashicorp/go-hclog v0.16.2 // indirect + github.com/hashicorp/go-secure-stdlib/strutil v0.1.1 // indirect + github.com/hashicorp/go-sockaddr v1.0.2 // indirect + github.com/mattn/go-colorable v0.1.6 // indirect + github.com/mattn/go-isatty v0.0.12 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/ryanuber/go-glob v1.0.0 // indirect + golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect + golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 // indirect + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect +) diff --git a/api/hostcatalogs/host_catalog.gen.go b/api/hostcatalogs/host_catalog.gen.go index ffa8f3856..ae121ae42 100644 --- a/api/hostcatalogs/host_catalog.gen.go +++ b/api/hostcatalogs/host_catalog.gen.go @@ -9,6 +9,7 @@ import ( "time" "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/plugins" "github.com/hashicorp/boundary/api/scopes" ) @@ -16,6 +17,8 @@ type HostCatalog struct { Id string `json:"id,omitempty"` ScopeId string `json:"scope_id,omitempty"` Scope *scopes.ScopeInfo `json:"scope,omitempty"` + PluginId string `json:"plugin_id,omitempty"` + Plugin *plugins.PluginInfo `json:"plugin,omitempty"` Name string `json:"name,omitempty"` Description string `json:"description,omitempty"` CreatedTime time.Time `json:"created_time,omitempty"` @@ -23,6 +26,7 @@ type HostCatalog struct { Version uint32 `json:"version,omitempty"` Type string `json:"type,omitempty"` Attributes map[string]interface{} `json:"attributes,omitempty"` + Secrets map[string]interface{} `json:"secrets,omitempty"` AuthorizedActions []string `json:"authorized_actions,omitempty"` AuthorizedCollectionActions map[string][]string `json:"authorized_collection_actions,omitempty"` diff --git a/api/hostcatalogs/option.gen.go b/api/hostcatalogs/option.gen.go index f968ede87..71daabb19 100644 --- a/api/hostcatalogs/option.gen.go +++ b/api/hostcatalogs/option.gen.go @@ -1,6 +1,7 @@ package hostcatalogs import ( + "fmt" "strconv" "strings" @@ -122,3 +123,27 @@ func DefaultName() Option { o.postMap["name"] = nil } } + +func WithPluginId(inPluginId string) Option { + return func(o *options) { + o.postMap["plugin_id"] = inPluginId + } +} + +func WithPluginName(inPluginName string) Option { + return func(o *options) { + o.queryMap["plugin_name"] = fmt.Sprintf("%v", inPluginName) + } +} + +func WithSecrets(inSecrets map[string]interface{}) Option { + return func(o *options) { + o.postMap["secrets"] = inSecrets + } +} + +func DefaultSecrets() Option { + return func(o *options) { + o.postMap["secrets"] = nil + } +} diff --git a/api/hosts/host.gen.go b/api/hosts/host.gen.go index 51b6b424a..718ffd667 100644 --- a/api/hosts/host.gen.go +++ b/api/hosts/host.gen.go @@ -9,6 +9,7 @@ import ( "time" "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/plugins" "github.com/hashicorp/boundary/api/scopes" ) @@ -16,6 +17,7 @@ type Host struct { Id string `json:"id,omitempty"` HostCatalogId string `json:"host_catalog_id,omitempty"` Scope *scopes.ScopeInfo `json:"scope,omitempty"` + Plugin *plugins.PluginInfo `json:"plugin,omitempty"` Name string `json:"name,omitempty"` Description string `json:"description,omitempty"` CreatedTime time.Time `json:"created_time,omitempty"` @@ -24,6 +26,8 @@ type Host struct { Type string `json:"type,omitempty"` HostSetIds []string `json:"host_set_ids,omitempty"` Attributes map[string]interface{} `json:"attributes,omitempty"` + IpAddresses []string `json:"ip_addresses,omitempty"` + DnsNames []string `json:"dns_names,omitempty"` AuthorizedActions []string `json:"authorized_actions,omitempty"` response *api.Response diff --git a/api/hostsets/host_set.gen.go b/api/hostsets/host_set.gen.go index d316e87cb..612c9b81b 100644 --- a/api/hostsets/host_set.gen.go +++ b/api/hostsets/host_set.gen.go @@ -9,22 +9,26 @@ import ( "time" "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/plugins" "github.com/hashicorp/boundary/api/scopes" ) type HostSet struct { - Id string `json:"id,omitempty"` - HostCatalogId string `json:"host_catalog_id,omitempty"` - Scope *scopes.ScopeInfo `json:"scope,omitempty"` - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - CreatedTime time.Time `json:"created_time,omitempty"` - UpdatedTime time.Time `json:"updated_time,omitempty"` - Version uint32 `json:"version,omitempty"` - Type string `json:"type,omitempty"` - HostIds []string `json:"host_ids,omitempty"` - Attributes map[string]interface{} `json:"attributes,omitempty"` - AuthorizedActions []string `json:"authorized_actions,omitempty"` + Id string `json:"id,omitempty"` + HostCatalogId string `json:"host_catalog_id,omitempty"` + Scope *scopes.ScopeInfo `json:"scope,omitempty"` + Plugin *plugins.PluginInfo `json:"plugin,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + CreatedTime time.Time `json:"created_time,omitempty"` + UpdatedTime time.Time `json:"updated_time,omitempty"` + Version uint32 `json:"version,omitempty"` + Type string `json:"type,omitempty"` + HostIds []string `json:"host_ids,omitempty"` + PreferredEndpoints []string `json:"preferred_endpoints,omitempty"` + SyncIntervalSeconds int32 `json:"sync_interval_seconds,omitempty"` + Attributes map[string]interface{} `json:"attributes,omitempty"` + AuthorizedActions []string `json:"authorized_actions,omitempty"` response *api.Response } diff --git a/api/hostsets/option.gen.go b/api/hostsets/option.gen.go index f27578eb9..a2b25f4dd 100644 --- a/api/hostsets/option.gen.go +++ b/api/hostsets/option.gen.go @@ -74,6 +74,18 @@ func WithFilter(filter string) Option { } } +func WithAttributes(inAttributes map[string]interface{}) Option { + return func(o *options) { + o.postMap["attributes"] = inAttributes + } +} + +func DefaultAttributes() Option { + return func(o *options) { + o.postMap["attributes"] = nil + } +} + func WithDescription(inDescription string) Option { return func(o *options) { o.postMap["description"] = inDescription @@ -97,3 +109,27 @@ func DefaultName() Option { o.postMap["name"] = nil } } + +func WithPreferredEndpoints(inPreferredEndpoints []string) Option { + return func(o *options) { + o.postMap["preferred_endpoints"] = inPreferredEndpoints + } +} + +func DefaultPreferredEndpoints() Option { + return func(o *options) { + o.postMap["preferred_endpoints"] = nil + } +} + +func WithSyncIntervalSeconds(inSyncIntervalSeconds int32) Option { + return func(o *options) { + o.postMap["sync_interval_seconds"] = inSyncIntervalSeconds + } +} + +func DefaultSyncIntervalSeconds() Option { + return func(o *options) { + o.postMap["sync_interval_seconds"] = nil + } +} diff --git a/api/plugins/plugin_info.gen.go b/api/plugins/plugin_info.gen.go new file mode 100644 index 000000000..2eb390ddd --- /dev/null +++ b/api/plugins/plugin_info.gen.go @@ -0,0 +1,8 @@ +// Code generated by "make api"; DO NOT EDIT. +package plugins + +type PluginInfo struct { + Id string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` +} diff --git a/globals/fields.go b/globals/fields.go index c6a90108e..684411135 100644 --- a/globals/fields.go +++ b/globals/fields.go @@ -58,6 +58,13 @@ const ( ApplicationCredentialLibrariesField = "application_credential_libraries" ApplicationCredentialSourceIdsField = "application_credential_source_ids" ApplicationCredentialSourcesField = "application_credential_sources" + PreferredEndpointsField = "preferred_endpoints" + SyncIntervalSecondsField = "sync_interval_seconds" + PluginIdField = "plugin_id" + PluginField = "plugin" + PluginNameField = "plugin_name" + IpAddressesField = "ip_addresses" + DnsNamesField = "dns_names" EgressCredentialSourceIdsField = "egress_credential_source_ids" EgressCredentialSourcesField = "egress_credential_sources" ) diff --git a/go.mod b/go.mod index 1284c4b41..6248bd7fa 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/hashicorp/boundary -go 1.16 +go 1.17 replace github.com/hashicorp/boundary/api => ./api @@ -9,8 +9,7 @@ replace github.com/hashicorp/boundary/sdk => ./sdk require ( github.com/armon/go-metrics v0.3.9 github.com/bufbuild/buf v0.37.0 - github.com/dhui/dktest v0.3.4 // indirect - github.com/fatih/color v1.12.0 + github.com/fatih/color v1.13.0 github.com/fatih/structs v1.1.0 github.com/favadi/protoc-go-inject-tag v1.3.0 github.com/godbus/dbus/v5 v5.0.4 // indirect @@ -20,8 +19,8 @@ require ( github.com/google/go-cmp v0.5.6 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.6.0 - github.com/hashicorp/boundary/api v0.0.19 - github.com/hashicorp/boundary/sdk v0.0.0-00010101000000-000000000000 + github.com/hashicorp/boundary/api v0.0.20-0.20211110201018-3f3f6d974cdb + github.com/hashicorp/boundary/sdk v0.0.12-0.20211110201018-3f3f6d974cdb github.com/hashicorp/cap v0.1.1 github.com/hashicorp/dawdle v0.4.0 github.com/hashicorp/dbassert v0.0.0-20210708202608-ecf920cf1ed8 @@ -34,7 +33,8 @@ require ( github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-retryablehttp v0.7.0 github.com/hashicorp/go-rootcerts v1.0.2 - github.com/hashicorp/go-secure-stdlib/base62 v0.1.1 + github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6 // indirect + github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 github.com/hashicorp/go-secure-stdlib/configutil v0.1.2 github.com/hashicorp/go-secure-stdlib/gatedwriter v0.1.1 github.com/hashicorp/go-secure-stdlib/kv-builder v0.1.1 @@ -54,7 +54,7 @@ require ( github.com/jefferai/keyring v1.1.7-0.20210105022822-8749b3d9ce79 github.com/kr/pretty v0.3.0 github.com/kr/text v0.2.0 - github.com/mattn/go-colorable v0.1.8 + github.com/mattn/go-colorable v0.1.11 github.com/mitchellh/cli v1.1.2 github.com/mitchellh/copystructure v1.2.0 github.com/mitchellh/go-wordwrap v1.0.1 @@ -67,12 +67,12 @@ require ( github.com/pires/go-proxyproto v0.6.1 github.com/pkg/errors v0.9.1 github.com/posener/complete v1.2.3 - github.com/spf13/cobra v1.1.1 // indirect + github.com/ryanuber/go-glob v1.0.0 github.com/stretchr/testify v1.7.0 github.com/zalando/go-keyring v0.1.1 go.uber.org/atomic v1.9.0 golang.org/x/crypto v0.0.0-20210915214749-c084706c2272 - golang.org/x/sys v0.0.0-20210917161153-d61c044b1678 + golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef golang.org/x/term v0.0.0-20210916214954-140adaaadfaf golang.org/x/tools v0.1.6 google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83 @@ -84,3 +84,131 @@ require ( mvdan.cc/gofumpt v0.1.1 nhooyr.io/websocket v1.8.7 ) + +require ( + cloud.google.com/go v0.65.0 // indirect + github.com/AlecAivazis/survey/v2 v2.2.9 // indirect + github.com/Azure/azure-sdk-for-go v58.0.0+incompatible // indirect + github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect + github.com/Azure/go-autorest v14.2.0+incompatible // indirect + github.com/Azure/go-autorest/autorest v0.11.21 // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.14 // indirect + github.com/Azure/go-autorest/autorest/azure/auth v0.5.8 // indirect + github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 // indirect + github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect + github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect + github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect + github.com/Azure/go-autorest/logger v0.2.1 // indirect + github.com/Azure/go-autorest/tracing v0.6.0 // indirect + github.com/Masterminds/goutils v1.1.0 // indirect + github.com/Masterminds/semver v1.5.0 // indirect + github.com/Masterminds/semver/v3 v3.1.1 // indirect + github.com/Masterminds/sprig v2.22.0+incompatible // indirect + github.com/Microsoft/go-winio v0.4.16 // indirect + github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect + github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190620160927-9418d7b0cd0f // indirect + github.com/apex/log v1.9.0 // indirect + github.com/armon/go-radix v1.0.0 // indirect + github.com/aws/aws-sdk-go v1.40.55 // indirect + github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect + github.com/bgentry/speakeasy v0.1.0 // indirect + github.com/cenkalti/backoff/v3 v3.0.0 // indirect + github.com/cenkalti/backoff/v4 v4.1.0 // indirect + github.com/containerd/continuity v0.0.0-20200709052629-daa8e1ccc0bc // indirect + github.com/coreos/go-oidc/v3 v3.0.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect + github.com/danieljoos/wincred v1.1.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dhui/dktest v0.3.4 // indirect + github.com/dimchansky/utfbom v1.1.1 // indirect + github.com/docker/cli v20.10.7+incompatible // indirect + github.com/docker/docker v20.10.7+incompatible // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-units v0.4.0 // indirect + github.com/dvsekhvalnov/jose2go v0.0.0-20200901110807-248326c1351b // indirect + github.com/form3tech-oss/jwt-go v3.2.2+incompatible // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/gofrs/flock v0.8.0 // indirect + github.com/gofrs/uuid v4.0.0+incompatible // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/glog v1.0.0 // indirect + github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect + github.com/golang/snappy v0.0.1 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/google/uuid v1.1.2 // indirect + github.com/googleapis/gax-go/v2 v2.0.5 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-immutable-radix v1.1.0 // indirect + github.com/hashicorp/go-plugin v1.0.1 // indirect + github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.1 // indirect + github.com/hashicorp/go-sockaddr v1.0.2 // indirect + github.com/hashicorp/go-version v1.3.0 // indirect + github.com/hashicorp/golang-lru v0.5.3 // indirect + github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect + github.com/huandu/xstrings v1.3.2 // indirect + github.com/imdario/mergo v0.3.12 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.1.1 // indirect + github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect + github.com/jackc/pgtype v1.7.0 // indirect + github.com/jefferai/go-libsecret v0.0.0-20210105015933-d08a58b018bc // indirect + github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f // indirect + github.com/jhump/protoreflect v1.8.1 // indirect + github.com/jinzhu/gorm v1.9.12 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.2 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/json-iterator/go v1.1.10 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d // indirect + github.com/klauspost/compress v1.11.7 // indirect + github.com/klauspost/pgzip v1.2.5 // indirect + github.com/lib/pq v1.8.0 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/go-testing-interface v1.0.0 // indirect + github.com/mitchellh/iochan v1.0.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/mtibben/percent v0.2.1 // indirect + github.com/oklog/run v1.0.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.0.1 // indirect + github.com/opencontainers/runc v1.0.0-rc9 // indirect + github.com/oracle/oci-go-sdk v12.5.0+incompatible // indirect + github.com/pierrec/lz4 v2.5.2+incompatible // indirect + github.com/pkg/profile v1.5.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rogpeppe/go-internal v1.6.2 // indirect + github.com/russross/blackfriday/v2 v2.0.1 // indirect + github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect + github.com/sirupsen/logrus v1.7.0 // indirect + github.com/spf13/cobra v1.1.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/twitchtv/twirp v7.1.0+incompatible // indirect + github.com/urfave/cli/v2 v2.3.0 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect + go.opencensus.io v0.22.6 // indirect + go.uber.org/multierr v1.6.0 // indirect + go.uber.org/zap v1.16.0 // indirect + golang.org/x/mod v0.4.2 // indirect + golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect + golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect + golang.org/x/text v0.3.6 // indirect + golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + google.golang.org/api v0.30.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + gopkg.in/ini.v1 v1.51.0 // indirect + gopkg.in/square/go-jose.v2 v2.5.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/go.sum b/go.sum index 61b79f5f4..fef78debf 100644 --- a/go.sum +++ b/go.sum @@ -39,38 +39,55 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AlecAivazis/survey/v2 v2.2.9 h1:LWvJtUswz/W9/zVVXELrmlvdwWcKE60ZAw0FWV9vssk= github.com/AlecAivazis/survey/v2 v2.2.9/go.mod h1:9DYvHgXtiXm6nCn+jXnOXLKbH+Yo9u8fAS/SduGdoPk= -github.com/Azure/azure-sdk-for-go v36.2.0+incompatible h1:09cv2WoH0g6jl6m2iT+R9qcIPZKhXEL0sbmLhxP895s= github.com/Azure/azure-sdk-for-go v36.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v58.0.0+incompatible h1:Cw16jiP4dI+CK761aq44ol4RV5dUiIIXky1+EKpoiVM= +github.com/Azure/azure-sdk-for-go v58.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0= -github.com/Azure/go-autorest/autorest v0.10.1 h1:uaB8A32IZU9YKs9v50+/LWIWTDHJk2vlGzbfd7FfESI= github.com/Azure/go-autorest/autorest v0.10.1/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= +github.com/Azure/go-autorest/autorest v0.11.17/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= +github.com/Azure/go-autorest/autorest v0.11.21 h1:w77zY/9RnUAWcIQyDC0Fc89mCvwftR8F+zsR/OH6enk= +github.com/Azure/go-autorest/autorest v0.11.21/go.mod h1:Do/yuMSW/13ayUkcVREpsMHGG+MvV81uzSCFgYPj4tM= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= -github.com/Azure/go-autorest/autorest/adal v0.8.2 h1:O1X4oexUxnZCaEUGsvMnr8ZGj8HI37tNezwY4npRqA0= github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= -github.com/Azure/go-autorest/autorest/azure/auth v0.4.2 h1:iM6UAvjR97ZIeR93qTcwpKNMpV+/FTWjwEbuPD495Tk= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/adal v0.9.11/go.mod h1:nBKAnTomx8gDtl+3ZCJv2v0KACFHWTB2drffI1B68Pk= +github.com/Azure/go-autorest/autorest/adal v0.9.14 h1:G8hexQdV5D4khOXrWG2YuLCFKhWYmWD8bHYaXN5ophk= +github.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM= -github.com/Azure/go-autorest/autorest/azure/cli v0.3.1 h1:LXl088ZQlP0SBppGFsRZonW6hSvwgL5gRByMbvUbx8U= +github.com/Azure/go-autorest/autorest/azure/auth v0.5.8 h1:TzPg6B6fTZ0G1zBf3T54aI7p3cAT6u//TOXGPmFMOXg= +github.com/Azure/go-autorest/autorest/azure/auth v0.5.8/go.mod h1:kxyKZTSfKh8OVFWPAgOgQ/frrJgeYQJPyR5fLFmXko4= github.com/Azure/go-autorest/autorest/azure/cli v0.3.1/go.mod h1:ZG5p860J94/0kI9mNJVoIoLgXcirM2gF5i2kWloofxw= +github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 h1:dMOmEJfkLKW/7JsokJqkyoYSgmR08hi9KrhjZb+JALY= +github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= -github.com/Azure/go-autorest/autorest/date v0.2.0 h1:yW+Zlqf26583pE43KhfnhFcdmSWlm5Ew6bxipnr/tbM= github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.3.0 h1:qJumjCaCudz+OcqE9/XtEPfvtOjOmKaui4EOpFI6zZc= github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= -github.com/Azure/go-autorest/autorest/to v0.3.0 h1:zebkZaadz7+wIQYgC7GXaz3Wb28yKYfVkkBKwc38VF8= +github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= -github.com/Azure/go-autorest/autorest/validation v0.2.0 h1:15vMO4y76dehZSq7pAaOLQxC6dZYsSrj2GQpflyM/L4= +github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= +github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI= -github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY= +github.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac= +github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= -github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -132,8 +149,9 @@ github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.30.27 h1:9gPjZWVDSoQrBO2AvqrWObS6KAZByfEJxQoCYo4ZfK0= github.com/aws/aws-sdk-go v1.30.27/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go v1.40.55 h1:l5xNBmafEAfdJIIe32QbTLG3j/JscH8fzZqYyMlntuI= +github.com/aws/aws-sdk-go v1.40.55/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= @@ -223,14 +241,14 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec h1:NfhRXXFDPxcF5Cwo06DzeIaE7uuJtAUhsDwH3LNsjos= github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dhui/dktest v0.3.3/go.mod h1:EML9sP4sqJELHn4jV7B0TY8oF6077nk83/tz7M56jcQ= github.com/dhui/dktest v0.3.4 h1:VbUEcaSP+U2/yUr9d2JhSThXYEnDlGabRSHe2rIE46E= github.com/dhui/dktest v0.3.4/go.mod h1:4m4n6lmXlmVfESth7mzdcv8nBI5mOb5UROPqjM02csU= -github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= +github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= +github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/docker/cli v20.10.7+incompatible h1:pv/3NqibQKphWZiAskMzdz8w0PRbtTaEB+f6NwdU7Is= github.com/docker/cli v20.10.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= @@ -257,7 +275,6 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= @@ -265,12 +282,15 @@ github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.11.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= -github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc= github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/favadi/protoc-go-inject-tag v1.3.0 h1:JPrmsmc/uBShG85uY5xGZIa5WJ0IaNZn6LZhQR9tIQE= github.com/favadi/protoc-go-inject-tag v1.3.0/go.mod h1:SSkUBgfqw2IJ2p7NPNKWk0Idwxt/qIt2LQgFPUgRGtc= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/frankban/quicktest v1.10.0 h1:Gfh+GAJZOAoKZsIZeZbdn2JF10kN1XHNvjsvQK8gVkE= @@ -489,6 +509,7 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-plugin v1.0.1 h1:4OtAfUGbnKC6yS48p0CtMX2oFYtzFZVv6rok3cRWgnE= github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.6.2/go.mod h1:gEx6HMUGxYYhJScX7W1Il64m6cc2C1mDaW3NQ9sY1FY= @@ -501,10 +522,12 @@ github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR3 github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-secure-stdlib/awsutil v0.1.1/go.mod h1:QRJZ7siKie+SZJB9jLbfKrs0Gd0yPWMtbneg0iU1PrY= -github.com/hashicorp/go-secure-stdlib/awsutil v0.1.2 h1:AEcLbDoaRC9JMmtZXsuCykztH53rvHsQFnwhoKtpNQM= github.com/hashicorp/go-secure-stdlib/awsutil v0.1.2/go.mod h1:QRJZ7siKie+SZJB9jLbfKrs0Gd0yPWMtbneg0iU1PrY= -github.com/hashicorp/go-secure-stdlib/base62 v0.1.1 h1:6KMBnfEv0/kLAz0O76sliN5mXbCDcLfs2kP7ssP7+DQ= +github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6 h1:W9WN8p6moV1fjKLkeqEgkAMu5rauy9QeYDAmIaPuuiA= +github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6/go.mod h1:MpCPSPGLDILGb4JMm94/mMi3YysIqsXzGCzkEZjcjXg= github.com/hashicorp/go-secure-stdlib/base62 v0.1.1/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw= +github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng= +github.com/hashicorp/go-secure-stdlib/base62 v0.1.2/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw= github.com/hashicorp/go-secure-stdlib/configutil v0.1.2 h1:ai0P91rxlyGWkYUH/zFr7mduW2Q+2FMrXawkLM8e7NU= github.com/hashicorp/go-secure-stdlib/configutil v0.1.2/go.mod h1:EN1DJMjv9y5e/uRAuP2WPibAox5KHEiq2BjZl00aNPQ= github.com/hashicorp/go-secure-stdlib/gatedwriter v0.1.1 h1:9um9R8i0+HbRHS9d64kdvWR0/LJvo12sIonvR9zr1+U= @@ -535,8 +558,9 @@ github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2I github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw= +github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -556,6 +580,7 @@ github.com/hashicorp/vault/sdk v0.1.14-0.20200519221530-14615acda45f/go.mod h1:W github.com/hashicorp/vault/sdk v0.2.1 h1:S4O6Iv/dyKlE9AUTXGa7VOvZmsCvg36toPKgV4f2P4M= github.com/hashicorp/vault/sdk v0.2.1/go.mod h1:WfUiO1vYzfBkz1TmoE4ZGU7HD0T0Cl/rZwaxjBkgN4U= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= @@ -654,8 +679,11 @@ github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/ github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI= github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0= @@ -725,8 +753,10 @@ github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcncea github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -736,8 +766,9 @@ github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2y github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= @@ -757,6 +788,7 @@ github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= @@ -805,6 +837,7 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS github.com/neo4j/neo4j-go-driver v1.8.1-0.20200803113522-b626aa943eba/go.mod h1:ncO5VaFWh0Nrt+4KT4mOZboaczBZcLuHrG+/sUeP8gI= github.com/nishanths/predeclared v0.0.0-20200524104333-86fad755b4d3/go.mod h1:nt3d53pc1VYcphSCIaYAJtnPYnr3Zyn8fMq2wvPGPso= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= @@ -1084,6 +1117,7 @@ golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= @@ -1177,6 +1211,7 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1262,8 +1297,9 @@ golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210917161153-d61c044b1678 h1:J27LZFQBFoihqXoegpscI10HpjZ7B5WQLLKL2FZXQKw= -golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef h1:fPxZ3Umkct3LZ8gK9nbk+DWDJ9fstZa2grBn+lWVKPs= +golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210916214954-140adaaadfaf h1:Ihq/mm/suC88gF8WFcVwk+OV6Tq+wyA1O0E5UEvDglI= @@ -1383,8 +1419,9 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -1452,7 +1489,6 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv google.golang.org/grpc v1.35.0-dev.0.20201218190559-666aea1fb34c/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE= diff --git a/internal/api/genapi/input.go b/internal/api/genapi/input.go index a3129014a..d06528493 100644 --- a/internal/api/genapi/input.go +++ b/internal/api/genapi/input.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hosts" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/managedgroups" + "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/plugins" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/roles" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/sessions" @@ -94,13 +95,13 @@ type structInfo struct { // listing recursiveListing bool - // extraOptions allows specifying extra options that will be created for a + // extraFields allows specifying extra options that will be created for a // given type, e.g. arguments only valid for one call or purpose and not // conveyed within the item itself - extraOptions []fieldInfo + extraFields []fieldInfo // fieldOverrides allows overriding some field behavior without making them - // "new" fields like with extraOptions + // "new" fields like with extraFields fieldOverrides []fieldInfo // createResponseTypes controls for which structs response types are created @@ -138,6 +139,11 @@ var inputStructs = []*structInfo{ outFile: "scopes/scope_info.gen.go", skipOptions: true, }, + { + inProto: &plugins.PluginInfo{}, + outFile: "plugins/plugin_info.gen.go", + skipOptions: true, + }, { inProto: &scopes.Scope{}, outFile: "scopes/scope.gen.go", @@ -150,7 +156,7 @@ var inputStructs = []*structInfo{ listTemplate, }, pluralResourceName: "scopes", - extraOptions: []fieldInfo{ + extraFields: []fieldInfo{ { Name: "SkipAdminRoleCreation", ProtoName: "skip_admin_role_creation", @@ -502,6 +508,21 @@ var inputStructs = []*structInfo{ deleteTemplate, listTemplate, }, + extraFields: []fieldInfo{ + { + Name: "PluginName", + ProtoName: "plugin_name", + FieldType: "string", + SkipDefault: true, + Query: true, + }, + }, + fieldOverrides: []fieldInfo{ + { + Name: "PluginId", + SkipDefault: true, + }, + }, pluralResourceName: "host-catalogs", typeOnCreate: true, versionEnabled: true, @@ -620,7 +641,7 @@ var inputStructs = []*structInfo{ "CredentialLibraries": {}, "CredentialSources": {}, }, - extraOptions: []fieldInfo{ + extraFields: []fieldInfo{ { Name: "HostId", ProtoName: "host_id", diff --git a/internal/api/genapi/templates.go b/internal/api/genapi/templates.go index 8bfc3c409..5b5386c99 100644 --- a/internal/api/genapi/templates.go +++ b/internal/api/genapi/templates.go @@ -44,7 +44,7 @@ type templateInput struct { ResourcePath string ParentTypeName string SliceSubtypes map[string]sliceSubtypeInfo - ExtraOptions []fieldInfo + ExtraFields []fieldInfo VersionEnabled bool TypeOnCreate bool CreateResponseTypes bool @@ -63,7 +63,7 @@ func fillTemplates() { Fields: in.generatedStructure.fields, PluralResourceName: in.pluralResourceName, ParentTypeName: in.parentTypeName, - ExtraOptions: in.extraOptions, + ExtraFields: in.extraFields, VersionEnabled: in.versionEnabled, TypeOnCreate: in.typeOnCreate, CreateResponseTypes: in.createResponseTypes, @@ -129,13 +129,13 @@ func fillTemplates() { } optionsMap[input.Package] = optionMap } - // Add in extra defined options - if len(in.extraOptions) > 0 { + // Add in extra defined fields + if len(in.extraFields) > 0 { optionMap := optionsMap[input.Package] if optionMap == nil { optionMap = map[string]fieldInfo{} } - for _, val := range in.extraOptions { + for _, val := range in.extraFields { optionMap[val.Name] = val } optionsMap[input.Package] = optionMap diff --git a/internal/auth/subtype.go b/internal/auth/subtype.go index fbcd8e4d3..43c19cefc 100644 --- a/internal/auth/subtype.go +++ b/internal/auth/subtype.go @@ -1,7 +1,7 @@ package auth import ( - "github.com/hashicorp/boundary/internal/db/timestamp" + "github.com/hashicorp/boundary/internal/boundary" "github.com/hashicorp/boundary/internal/types/subtypes" ) @@ -28,32 +28,17 @@ func Register(subtype subtypes.Subtype, prefixes ...string) error { // AuthMethod contains the common methods across all the different types of auth methods. type AuthMethod interface { - GetPublicId() string - GetCreateTime() *timestamp.Timestamp - GetUpdateTime() *timestamp.Timestamp - GetName() string - GetDescription() string + boundary.Resource GetScopeId() string - GetVersion() uint32 GetIsPrimaryAuthMethod() bool } type Account interface { - GetPublicId() string - GetCreateTime() *timestamp.Timestamp - GetUpdateTime() *timestamp.Timestamp - GetName() string - GetDescription() string + boundary.Resource GetAuthMethodId() string - GetVersion() uint32 } type ManagedGroup interface { - GetPublicId() string - GetCreateTime() *timestamp.Timestamp - GetUpdateTime() *timestamp.Timestamp - GetName() string - GetDescription() string + boundary.Resource GetAuthMethodId() string - GetVersion() uint32 } diff --git a/internal/cmd/base/dev.go b/internal/cmd/base/dev.go index 589081f01..375616ff3 100644 --- a/internal/cmd/base/dev.go +++ b/internal/cmd/base/dev.go @@ -16,11 +16,11 @@ import ( "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/observability/event" + hostplugin "github.com/hashicorp/boundary/internal/plugin/host" "github.com/hashicorp/boundary/internal/types/scope" "github.com/hashicorp/boundary/testing/dbtest" capoidc "github.com/hashicorp/cap/oidc" "github.com/hashicorp/go-multierror" - "gorm.io/gorm/logger" ) func (b *Server) CreateDevDatabase(ctx context.Context, opt ...Option) error { @@ -101,8 +101,6 @@ func (b *Server) CreateDevDatabase(ctx context.Context, opt ...Option) error { return err } - b.Database.Config.Logger.LogMode(logger.Info) - if err := b.CreateGlobalKmsKeys(ctx); err != nil { if c != nil { err = multierror.Append(err, c()) @@ -145,6 +143,18 @@ func (b *Server) CreateDevDatabase(ctx context.Context, opt ...Option) error { return err } + if opts.withHostPlugin != nil { + pluginId, plg := opts.withHostPlugin() + b.DevLoopbackHostPluginId = pluginId + opts := []hostplugin.Option{ + hostplugin.WithDescription("Provides an initial loopback host plugin in Boundary"), + hostplugin.WithPublicId(b.DevLoopbackHostPluginId), + } + if _, err = b.RegisterHostPlugin(ctx, "loopback", plg, opts...); err != nil { + return err + } + } + if opts.withSkipHostResourcesCreation { // now that we have passed all the error cases, reset c to be a noop so the // defer doesn't do anything. diff --git a/internal/cmd/base/format.go b/internal/cmd/base/format.go index 55ff54b9d..ea269680b 100644 --- a/internal/cmd/base/format.go +++ b/internal/cmd/base/format.go @@ -10,6 +10,7 @@ import ( "unicode/utf8" "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/plugins" "github.com/hashicorp/boundary/api/scopes" "github.com/mitchellh/cli" "github.com/mitchellh/go-wordwrap" @@ -34,6 +35,17 @@ func ScopeInfoForOutput(scp *scopes.ScopeInfo, maxLength int) string { return WrapMap(4, maxLength, vals) } +func PluginInfoForOutput(plg *plugins.PluginInfo, maxLength int) string { + if plg == nil { + return " " + } + vals := map[string]interface{}{ + "ID": plg.Id, + "Name": plg.Name, + } + return WrapMap(4, maxLength, vals) +} + func MaxAttributesLength(nonAttributesMap, attributesMap map[string]interface{}, keySubstMap map[string]string) int { // We always print a scope ID and in some cases this particular key ends up // being the longest key, so start with it as a baseline. It's always diff --git a/internal/cmd/base/initial_resources.go b/internal/cmd/base/initial_resources.go index 8bd84a31b..fb19631fc 100644 --- a/internal/cmd/base/initial_resources.go +++ b/internal/cmd/base/initial_resources.go @@ -10,10 +10,12 @@ import ( "github.com/hashicorp/boundary/internal/host/static" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" + hostplugin "github.com/hashicorp/boundary/internal/plugin/host" "github.com/hashicorp/boundary/internal/servers/controller/auth" "github.com/hashicorp/boundary/internal/target" "github.com/hashicorp/boundary/internal/target/tcp" "github.com/hashicorp/boundary/internal/types/scope" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" "github.com/hashicorp/go-secure-stdlib/base62" ) @@ -588,3 +590,64 @@ func (b *Server) CreateInitialTarget(ctx context.Context) (target.Target, error) return tt, nil } + +// RegisterHostPlugin creates a host plugin in the database if not present. +// It also registers the plugin in the shared map of running plugins. Since +// all boundary provided host plugins must have a name, a name is required +// when calling RegisterHostPlugin and will be used even if WithName is provided. +func (b *Server) RegisterHostPlugin(ctx context.Context, name string, plg plgpb.HostPluginServiceClient, opt ...hostplugin.Option) (*hostplugin.Plugin, error) { + if name == "" { + return nil, fmt.Errorf("no name provided when creating plugin.") + } + rw := db.New(b.Database) + + kmsRepo, err := kms.NewRepository(rw, rw) + if err != nil { + return nil, fmt.Errorf("error creating kms repository: %w", err) + } + kmsCache, err := kms.NewKms(kmsRepo) + if err != nil { + return nil, fmt.Errorf("error creating kms cache: %w", err) + } + if err := kmsCache.AddExternalWrappers( + kms.WithRootWrapper(b.RootKms), + ); err != nil { + return nil, fmt.Errorf("error adding config keys to kms: %w", err) + } + + cancelCtx, cancel := context.WithCancel(ctx) + defer cancel() + go func() { + select { + case <-b.ShutdownCh: + cancel() + case <-cancelCtx.Done(): + } + }() + + hpRepo, err := hostplugin.NewRepository(rw, rw, kmsCache) + if err != nil { + return nil, fmt.Errorf("error creating host plugin repository: %w", err) + } + + plugin, err := hpRepo.LookupPluginByName(ctx, name) + if err != nil { + return nil, fmt.Errorf("error looking up host plugin by name: %w", err) + } + + if plugin == nil { + opt = append(opt, hostplugin.WithName(name)) + plugin = hostplugin.NewPlugin(opt...) + plugin, err = hpRepo.CreatePlugin(cancelCtx, plugin, opt...) + if err != nil { + return nil, fmt.Errorf("error creating host plugin: %w", err) + } + } + + if b.HostPlugins == nil { + b.HostPlugins = make(map[string]plgpb.HostPluginServiceClient) + } + b.HostPlugins[plugin.GetPublicId()] = plg + + return plugin, nil +} diff --git a/internal/cmd/base/option.go b/internal/cmd/base/option.go index d1f006475..ef2888ede 100644 --- a/internal/cmd/base/option.go +++ b/internal/cmd/base/option.go @@ -2,6 +2,7 @@ package base import ( "github.com/hashicorp/boundary/internal/observability/event" + "github.com/hashicorp/boundary/sdk/pbs/plugin" wrapping "github.com/hashicorp/go-kms-wrapping" ) @@ -37,6 +38,7 @@ type Options struct { withEventWrapper wrapping.Wrapper withAttributeFieldPrefix string withStatusCode int + withHostPlugin func() (string, plugin.HostPluginServiceClient) } func getDefaultOptions() Options { @@ -168,3 +170,13 @@ func WithDatabaseTemplate(template string) Option { o.withDatabaseTemplate = template } } + +// WithHostPlugin allows specifying a plugin ID and implementation to create at +// startup +func WithHostPlugin(pluginId string, plg plugin.HostPluginServiceClient) Option { + return func(o *Options) { + o.withHostPlugin = func() (string, plugin.HostPluginServiceClient) { + return pluginId, plg + } + } +} diff --git a/internal/cmd/base/servers.go b/internal/cmd/base/servers.go index ba2555aed..b4fdc1590 100644 --- a/internal/cmd/base/servers.go +++ b/internal/cmd/base/servers.go @@ -27,6 +27,7 @@ import ( "github.com/hashicorp/boundary/internal/observability/event" "github.com/hashicorp/boundary/internal/servers" "github.com/hashicorp/boundary/internal/types/scope" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" "github.com/hashicorp/boundary/version" "github.com/hashicorp/go-hclog" wrapping "github.com/hashicorp/go-kms-wrapping" @@ -111,6 +112,9 @@ type Server struct { DevTargetDefaultPort int DevTargetSessionMaxSeconds int DevTargetSessionConnectionLimit int + DevLoopbackHostPluginId string + + HostPlugins map[string]plgpb.HostPluginServiceClient DevOidcSetup oidcSetup diff --git a/internal/cmd/commands.go b/internal/cmd/commands.go index 1776da276..8cd7d9cc0 100644 --- a/internal/cmd/commands.go +++ b/internal/cmd/commands.go @@ -512,6 +512,12 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Func: "create", }, nil }, + "host-catalogs create plugin": func() (cli.Command, error) { + return &hostcatalogscmd.PluginCommand{ + Command: base.NewCommand(ui), + Func: "create", + }, nil + }, "host-catalogs update": func() (cli.Command, error) { return &hostcatalogscmd.Command{ Command: base.NewCommand(ui), @@ -524,6 +530,12 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Func: "update", }, nil }, + "host-catalogs update plugin": func() (cli.Command, error) { + return &hostcatalogscmd.PluginCommand{ + Command: base.NewCommand(ui), + Func: "update", + }, nil + }, "host-sets": func() (cli.Command, error) { return &hostsetscmd.Command{ @@ -560,6 +572,12 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Func: "create", }, nil }, + "host-sets create plugin": func() (cli.Command, error) { + return &hostsetscmd.PluginCommand{ + Command: base.NewCommand(ui), + Func: "create", + }, nil + }, "host-sets update": func() (cli.Command, error) { return &hostsetscmd.Command{ Command: base.NewCommand(ui), @@ -572,6 +590,12 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Func: "update", }, nil }, + "host-sets update plugin": func() (cli.Command, error) { + return &hostsetscmd.PluginCommand{ + Command: base.NewCommand(ui), + Func: "update", + }, nil + }, "host-sets add-hosts": func() (cli.Command, error) { return &hostsetscmd.Command{ Command: base.NewCommand(ui), diff --git a/internal/cmd/commands/database/migrate.go b/internal/cmd/commands/database/migrate.go index 514c453c3..9cfc38753 100644 --- a/internal/cmd/commands/database/migrate.go +++ b/internal/cmd/commands/database/migrate.go @@ -6,7 +6,10 @@ import ( "github.com/hashicorp/boundary/internal/cmd/base" "github.com/hashicorp/boundary/internal/cmd/config" "github.com/hashicorp/boundary/internal/errors" + host_plugin_assets "github.com/hashicorp/boundary/plugins/host" + external_host_plugins "github.com/hashicorp/boundary/sdk/plugins/host" "github.com/hashicorp/boundary/sdk/wrapper" + "github.com/hashicorp/go-hclog" wrapping "github.com/hashicorp/go-kms-wrapping" "github.com/hashicorp/go-secure-stdlib/mlock" "github.com/hashicorp/go-secure-stdlib/parseutil" @@ -128,6 +131,43 @@ func (c *MigrateCommand) Run(args []string) (retCode int) { }() } + _, awsCleanup, err := external_host_plugins.CreateHostPlugin( + c.Context, + "aws", + external_host_plugins.WithHostPluginsFilesystem("boundary-plugin-host-", host_plugin_assets.FileSystem()), + external_host_plugins.WithHostPluginExecutionDir(c.Config.Plugins.ExecutionDir), + external_host_plugins.WithLogger(hclog.NewNullLogger())) + if err != nil { + c.UI.Error(fmt.Errorf("Error creating dynamic host plugin: %w", err).Error()) + c.UI.Warn(base.WrapAtLength( + "Starting in Boundary 0.7.0, plugins are being introduced for " + + "various parts of the system. These plugins are created by writing " + + "out and executing plugin binaries. The migration function performed " + + "a check to ensure this system is capable of running plugins and " + + "encountered an error. The ability to run plugins will eventually " + + "become mandatory (for instance, for KMS implementations), so we are " + + "ensuring that it's feasible on a given system before migrating the " + + "database to the new version of Boundary that requires this capability. " + + "If your temporary directory is not writable and/or you cannot execute " + + "binaries in that directory, try setting the field " + + `"execution_dir" in the "plugins" block in the configuration file:`)) + c.UI.Warn(` +plugins { + execution_dir = +} +`) + c.UI.Warn(base.WrapAtLength( + "Otherwise, please file a bug at " + + "https://github.com/hashicorp/boundary/issues/new/choose and tell us " + + "what the error message is, along with details about your environment. " + + "We are committed to resolving any issues as quickly as possible.")) + return base.CommandCliError + } + if err := awsCleanup(); err != nil { + c.UI.Error(fmt.Errorf("Error running plugin cleanup function: %w", err).Error()) + return base.CommandCliError + } + dialect := "postgres" c.srv = base.NewServer(&base.Command{UI: c.UI}) diff --git a/internal/cmd/commands/dev/dev.go b/internal/cmd/commands/dev/dev.go index 37bd8f229..c390b2e64 100644 --- a/internal/cmd/commands/dev/dev.go +++ b/internal/cmd/commands/dev/dev.go @@ -10,11 +10,13 @@ import ( "runtime" "strings" "syscall" + "time" "github.com/hashicorp/boundary/internal/auth/oidc" "github.com/hashicorp/boundary/internal/auth/password" "github.com/hashicorp/boundary/internal/cmd/base" "github.com/hashicorp/boundary/internal/cmd/config" + "github.com/hashicorp/boundary/internal/host/plugin" "github.com/hashicorp/boundary/internal/host/static" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/intglobals" @@ -47,36 +49,38 @@ type Command struct { controller *controller.Controller worker *worker.Worker - flagLogLevel string - flagLogFormat string - flagCombineLogs bool - flagLoginName string - flagPassword string - flagUnprivilegedLoginName string - flagUnprivilegedPassword string - flagIdSuffix string - flagHostAddress string - flagTargetDefaultPort int - flagTargetSessionMaxSeconds int - flagTargetSessionConnectionLimit int - flagControllerAPIListenAddr string - flagControllerClusterListenAddr string - flagControllerPublicClusterAddr string - flagControllerOnly bool - flagWorkerAuthKey string - flagWorkerProxyListenAddr string - flagWorkerPublicAddr string - flagUiPassthroughDir string - flagRecoveryKey string - flagDatabaseUrl string - flagContainerImage string - flagDisableDatabaseDestruction bool - flagEventFormat string - flagAudit string - flagObservations string - flagSysEvents string - flagEveryEventAllowFilters []string - flagEveryEventDenyFilters []string + flagLogLevel string + flagLogFormat string + flagCombineLogs bool + flagLoginName string + flagPassword string + flagUnprivilegedLoginName string + flagUnprivilegedPassword string + flagIdSuffix string + flagHostAddress string + flagTargetDefaultPort int + flagTargetSessionMaxSeconds int + flagTargetSessionConnectionLimit int + flagControllerAPIListenAddr string + flagControllerClusterListenAddr string + flagControllerPublicClusterAddr string + flagControllerOnly bool + flagWorkerAuthKey string + flagWorkerProxyListenAddr string + flagWorkerPublicAddr string + flagUiPassthroughDir string + flagRecoveryKey string + flagDatabaseUrl string + flagContainerImage string + flagDisableDatabaseDestruction bool + flagEventFormat string + flagAudit string + flagObservations string + flagSysEvents string + flagEveryEventAllowFilters []string + flagEveryEventDenyFilters []string + flagCreateLoopbackHostCatalogPlugin bool + flagPluginExecutionDir string } func (c *Command) Synopsis() string { @@ -308,6 +312,19 @@ func (c *Command) Flags() *base.FlagSets { Usage: `The optional every event deny filter. May be specified multiple times.`, }) + f.StringVar(&base.StringVar{ + Name: "plugin-execution-dir", + Target: &c.flagPluginExecutionDir, + EnvVar: "BOUNDARY_DEV_PLUGIN_EXECUTION_DIR", + Usage: "Specifies where Boundary should write plugins that it is executing; if not set defaults to system temp directory.", + }) + + f.BoolVar(&base.BoolVar{ + Name: "create-loopback-hostcatalog-plugin", + Target: &c.flagCreateLoopbackHostCatalogPlugin, + Hidden: true, + }) + return set } @@ -343,6 +360,7 @@ func (c *Command) Run(args []string) int { c.UI.Error(fmt.Errorf("Error creating controller dev config: %w", err).Error()) return base.CommandUserError } + if c.flagWorkerAuthKey != "" { c.Config.DevWorkerAuthKey = c.flagWorkerAuthKey for _, kms := range c.Config.Seals { @@ -351,6 +369,13 @@ func (c *Command) Run(args []string) int { } } } + + c.DevLoginName = c.flagLoginName + c.DevPassword = c.flagPassword + c.DevUnprivilegedLoginName = c.flagUnprivilegedLoginName + c.DevUnprivilegedPassword = c.flagUnprivilegedPassword + c.DevTargetDefaultPort = c.flagTargetDefaultPort + c.Config.Plugins.ExecutionDir = c.flagPluginExecutionDir if c.flagIdSuffix != "" { if len(c.flagIdSuffix) != 10 { c.UI.Error("Invalid ID suffix, must be exactly 10 characters") @@ -375,19 +400,7 @@ func (c *Command) Run(args []string) int { c.DevHostId = fmt.Sprintf("%s_%s", static.HostPrefix, c.flagIdSuffix) c.DevTargetId = fmt.Sprintf("%s_%s", tcp.TargetPrefix, c.flagIdSuffix) } - if c.flagLoginName != "" { - c.DevLoginName = c.flagLoginName - } - if c.flagPassword != "" { - c.DevPassword = c.flagPassword - } - if c.flagUnprivilegedLoginName != "" { - c.DevUnprivilegedLoginName = c.flagUnprivilegedLoginName - } - if c.flagUnprivilegedPassword != "" { - c.DevUnprivilegedPassword = c.flagUnprivilegedPassword - } - c.DevTargetDefaultPort = c.flagTargetDefaultPort + host, port, err := net.SplitHostPort(c.flagHostAddress) if err != nil { if !strings.Contains(err.Error(), "missing port") { @@ -547,6 +560,10 @@ func (c *Command) Run(args []string) int { }() var opts []base.Option + if c.flagCreateLoopbackHostCatalogPlugin { + opts = append(opts, base.WithHostPlugin("pl_1234567890", plugin.NewWrappingPluginClient(plugin.NewLoopbackPlugin()))) + c.Config.Controller.SchedulerRunJobInterval = 100 * time.Millisecond + } switch c.flagDatabaseUrl { case "": if c.flagDisableDatabaseDestruction { diff --git a/internal/cmd/commands/hostcatalogscmd/funcs.go b/internal/cmd/commands/hostcatalogscmd/funcs.go index e6022239b..273ab3d1f 100644 --- a/internal/cmd/commands/hostcatalogscmd/funcs.go +++ b/internal/cmd/commands/hostcatalogscmd/funcs.go @@ -83,6 +83,11 @@ func (c *Command) printListTable(items []*hostcatalogs.HostCatalog) string { fmt.Sprintf(" Scope ID: %s", m.ScopeId), ) } + if m.PluginId != "" { + output = append(output, + fmt.Sprintf(" Plugin ID: %s", m.PluginId), + ) + } if m.Version > 0 { output = append(output, fmt.Sprintf(" Version: %d", m.Version), @@ -135,6 +140,9 @@ func printItemTable(result api.GenericResult) string { if item.Description != "" { nonAttributeMap["Description"] = item.Description } + if item.PluginId != "" { + nonAttributeMap["Plugin ID"] = item.PluginId + } if item.Type != "" { nonAttributeMap["Type"] = item.Type } @@ -155,6 +163,22 @@ func printItemTable(result api.GenericResult) string { ) } + if item.Plugin != nil { + ret = append(ret, + "", + " Plugin:", + base.PluginInfoForOutput(item.Plugin, maxLength), + ) + } + + if len(item.Attributes) > 0 { + ret = append(ret, + "", + " Attributes:", + base.WrapMap(4, maxLength, item.Attributes), + ) + } + if len(item.AuthorizedActions) > 0 { ret = append(ret, "", @@ -180,14 +204,6 @@ func printItemTable(result api.GenericResult) string { } } - if len(item.Attributes) > 0 { - ret = append(ret, - "", - " Attributes:", - base.WrapMap(4, maxLength, item.Attributes), - ) - } - return base.WrapForHelpText(ret) } diff --git a/internal/cmd/commands/hostcatalogscmd/plugin_funcs.go b/internal/cmd/commands/hostcatalogscmd/plugin_funcs.go new file mode 100644 index 000000000..5bf4ac915 --- /dev/null +++ b/internal/cmd/commands/hostcatalogscmd/plugin_funcs.go @@ -0,0 +1,33 @@ +package hostcatalogscmd + +import ( + "github.com/hashicorp/boundary/internal/cmd/base" +) + +func (c *PluginCommand) extraPluginHelpFunc(helpMap map[string]func() string) string { + var helpStr string + switch c.Func { + case "create": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary host-catalogs create plugin [options] [args]", + "", + " Create a plugin-type host catalog. Example:", + "", + ` $ boundary host-catalogs create plugin -scope-id p_1234567890 -name prodops -description "Plugin host-catalog for ProdOps"`, + "", + "", + }) + + case "update": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary host-catalogs update plugin [options] [args]", + "", + " Update a plugin-type host catalog given its ID. Example:", + "", + ` $ boundary host-catalogs update plugin -id hc_1234567890 -name "devops" -description "Plugin host-catalog for DevOps"`, + "", + "", + }) + } + return helpStr + c.Flags().Help() +} diff --git a/internal/cmd/commands/hostcatalogscmd/plugin_hostcatalogs.gen.go b/internal/cmd/commands/hostcatalogscmd/plugin_hostcatalogs.gen.go new file mode 100644 index 000000000..41b1fb8dd --- /dev/null +++ b/internal/cmd/commands/hostcatalogscmd/plugin_hostcatalogs.gen.go @@ -0,0 +1,291 @@ +// Code generated by "make cli"; DO NOT EDIT. +package hostcatalogscmd + +import ( + "errors" + "fmt" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/hostcatalogs" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/go-secure-stdlib/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initPluginFlags() { + flagsOnce.Do(func() { + extraFlags := extraPluginActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsPluginMap[k] = append(flagsPluginMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*PluginCommand)(nil) + _ cli.CommandAutocomplete = (*PluginCommand)(nil) +) + +type PluginCommand struct { + *base.Command + + Func string + + plural string +} + +func (c *PluginCommand) AutocompleteArgs() complete.Predictor { + initPluginFlags() + return complete.PredictAnything +} + +func (c *PluginCommand) AutocompleteFlags() complete.Flags { + initPluginFlags() + return c.Flags().Completions() +} + +func (c *PluginCommand) Synopsis() string { + if extra := extraPluginSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "host catalog" + + synopsisStr = fmt.Sprintf("%s %s", "plugin-type", synopsisStr) + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *PluginCommand) Help() string { + initPluginFlags() + + var helpStr string + helpMap := common.HelpMap("host catalog") + + switch c.Func { + default: + + helpStr = c.extraPluginHelpFunc(helpMap) + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsPluginMap = map[string][]string{ + + "create": {"scope-id", "name", "description", "plugin-id", "plugin-name", "attributes", "attr", "string-attr", "bool-attr", "num-attr", "secrets", "secret", "string-secret", "bool-secret", "num-secret"}, + + "update": {"id", "name", "description", "version", "attributes", "attr", "string-attr", "bool-attr", "num-attr", "secrets", "secret", "string-secret", "bool-secret", "num-secret"}, +} + +func (c *PluginCommand) Flags() *base.FlagSets { + if len(flagsPluginMap[c.Func]) == 0 { + return c.FlagSet(base.FlagSetNone) + } + + set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) + f := set.NewFlagSet("Command Options") + common.PopulateCommonFlags(c.Command, f, "plugin-type host catalog", flagsPluginMap, c.Func) + + f = set.NewFlagSet("Attribute Options") + common.PopulateAttributeFlags(c.Command, f, flagsPluginMap, c.Func) + + f = set.NewFlagSet("Secrets Options") + common.PopulateSecretFlags(c.Command, f, flagsPluginMap, c.Func) + + extraPluginFlagsFunc(c, set, f) + + return set +} + +func (c *PluginCommand) Run(args []string) int { + initPluginFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + } + + c.plural = "plugin-type host catalog" + switch c.Func { + case "list": + c.plural = "plugin-type host catalogs" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.PrintCliError(err) + return base.CommandUserError + } + + if strutil.StrListContains(flagsPluginMap[c.Func], "id") && c.FlagId == "" { + c.PrintCliError(errors.New("ID is required but not passed in via -id")) + return base.CommandUserError + } + + var opts []hostcatalogs.Option + + if strutil.StrListContains(flagsPluginMap[c.Func], "scope-id") { + switch c.Func { + case "create": + if c.FlagScopeId == "" { + c.PrintCliError(errors.New("Scope ID must be passed in via -scope-id or BOUNDARY_SCOPE_ID")) + return base.CommandUserError + } + } + } + + client, err := c.Client() + if err != nil { + c.PrintCliError(fmt.Errorf("Error creating API client: %s", err.Error())) + return base.CommandCliError + } + hostcatalogsClient := hostcatalogs.NewClient(client) + + switch c.FlagName { + case "": + case "null": + opts = append(opts, hostcatalogs.DefaultName()) + default: + opts = append(opts, hostcatalogs.WithName(c.FlagName)) + } + + switch c.FlagDescription { + case "": + case "null": + opts = append(opts, hostcatalogs.DefaultDescription()) + default: + opts = append(opts, hostcatalogs.WithDescription(c.FlagDescription)) + } + + switch c.FlagRecursive { + case true: + opts = append(opts, hostcatalogs.WithRecursive(true)) + } + + if c.FlagFilter != "" { + opts = append(opts, hostcatalogs.WithFilter(c.FlagFilter)) + } + + switch c.FlagPluginId { + case "": + default: + opts = append(opts, hostcatalogs.WithPluginId(c.FlagPluginId)) + } + switch c.FlagPluginName { + case "": + default: + opts = append(opts, hostcatalogs.WithPluginName(c.FlagPluginName)) + } + + var version uint32 + + switch c.Func { + case "update": + switch c.FlagVersion { + case 0: + opts = append(opts, hostcatalogs.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + } + + if err := common.HandleAttributeFlags( + c.Command, + "attr", + c.FlagAttributes, + c.FlagAttrs, + func() { + opts = append(opts, hostcatalogs.DefaultAttributes()) + }, + func(in map[string]interface{}) { + opts = append(opts, hostcatalogs.WithAttributes(in)) + }); err != nil { + c.PrintCliError(fmt.Errorf("Error evaluating attribute flags to: %s", err.Error())) + return base.CommandCliError + } + + if err := common.HandleAttributeFlags( + c.Command, + "secret", + c.FlagSecrets, + c.FlagScrts, + func() { + opts = append(opts, hostcatalogs.DefaultSecrets()) + }, + func(in map[string]interface{}) { + opts = append(opts, hostcatalogs.WithSecrets(in)) + }); err != nil { + c.PrintCliError(fmt.Errorf("Error evaluating secret flags to: %s", err.Error())) + return base.CommandCliError + } + + if ok := extraPluginFlagsHandlingFunc(c, f, &opts); !ok { + return base.CommandUserError + } + + var result api.GenericResult + + switch c.Func { + + case "create": + result, err = hostcatalogsClient.Create(c.Context, "plugin", c.FlagScopeId, opts...) + + case "update": + result, err = hostcatalogsClient.Update(c.Context, c.FlagId, version, opts...) + + } + + result, err = executeExtraPluginActions(c, result, err, hostcatalogsClient, version, opts) + + if err != nil { + if apiErr := api.AsServerError(err); apiErr != nil { + var opts []base.Option + + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...) + return base.CommandApiError + } + c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) + return base.CommandCliError + } + + output, err := printCustomPluginActionOutput(c) + if err != nil { + c.PrintCliError(err) + return base.CommandUserError + } + if output { + return base.CommandSuccess + } + + switch c.Func { + } + + switch base.Format(c.UI) { + case "table": + c.UI.Output(printItemTable(result)) + + case "json": + if ok := c.PrintJsonItem(result); !ok { + return base.CommandCliError + } + } + + return base.CommandSuccess +} + +var ( + extraPluginActionsFlagsMapFunc = func() map[string][]string { return nil } + extraPluginSynopsisFunc = func(*PluginCommand) string { return "" } + extraPluginFlagsFunc = func(*PluginCommand, *base.FlagSets, *base.FlagSet) {} + extraPluginFlagsHandlingFunc = func(*PluginCommand, *base.FlagSets, *[]hostcatalogs.Option) bool { return true } + executeExtraPluginActions = func(_ *PluginCommand, inResult api.GenericResult, inErr error, _ *hostcatalogs.Client, _ uint32, _ []hostcatalogs.Option) (api.GenericResult, error) { + return inResult, inErr + } + printCustomPluginActionOutput = func(*PluginCommand) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/hostcatalogscmd/static_funcs.go b/internal/cmd/commands/hostcatalogscmd/static_funcs.go index e0a571c1d..48d5daee8 100644 --- a/internal/cmd/commands/hostcatalogscmd/static_funcs.go +++ b/internal/cmd/commands/hostcatalogscmd/static_funcs.go @@ -13,7 +13,7 @@ func (c *StaticCommand) extraStaticHelpFunc(helpMap map[string]func() string) st "", " Create a static-type host catalog. Example:", "", - ` $ boundary host-catalogs create static -name prodops -description "Static host-catalog for ProdOps"`, + ` $ boundary host-catalogs create static -scope-id p_1234567890 -name prodops -description "Static host-catalog for ProdOps"`, "", "", }) diff --git a/internal/cmd/commands/hostscmd/funcs.go b/internal/cmd/commands/hostscmd/funcs.go index ed6c292d9..95c61b5cb 100644 --- a/internal/cmd/commands/hostscmd/funcs.go +++ b/internal/cmd/commands/hostscmd/funcs.go @@ -56,7 +56,7 @@ func (c *Command) extraHelpFunc(helpMap map[string]func() string) string { func (c *Command) printListTable(items []*hosts.Host) string { if len(items) == 0 { - return "No host sets found" + return "No hosts found" } var output []string diff --git a/internal/cmd/commands/hostsetscmd/funcs.go b/internal/cmd/commands/hostsetscmd/funcs.go index b3064c8af..c6477b38e 100644 --- a/internal/cmd/commands/hostsetscmd/funcs.go +++ b/internal/cmd/commands/hostsetscmd/funcs.go @@ -215,6 +215,11 @@ func (c *Command) printListTable(items []*hostsets.HostSet) string { fmt.Sprintf(" Description: %s", item.Description), ) } + if item.SyncIntervalSeconds != 0 { + output = append(output, + fmt.Sprintf(" Sync Interval: %d seconds", item.SyncIntervalSeconds), + ) + } if len(item.AuthorizedActions) > 0 { output = append(output, " Authorized Actions:", @@ -253,6 +258,12 @@ func printItemTable(result api.GenericResult) string { if item.HostCatalogId != "" { nonAttributeMap["Host Catalog ID"] = item.HostCatalogId } + if item.PreferredEndpoints != nil { + nonAttributeMap["Preferred Endpoints"] = item.PreferredEndpoints + } + if item.SyncIntervalSeconds != 0 { + nonAttributeMap["Sync Interval"] = fmt.Sprintf("%d seconds", item.SyncIntervalSeconds) + } maxLength := base.MaxAttributesLength(nonAttributeMap, item.Attributes, keySubstMap) @@ -270,6 +281,22 @@ func printItemTable(result api.GenericResult) string { ) } + if item.Plugin != nil { + ret = append(ret, + "", + " Plugin:", + base.PluginInfoForOutput(item.Plugin, maxLength), + ) + } + + if len(item.Attributes) > 0 { + ret = append(ret, + "", + " Attributes:", + base.WrapMap(4, maxLength, item.Attributes), + ) + } + if len(item.AuthorizedActions) > 0 { ret = append(ret, "", @@ -286,14 +313,6 @@ func printItemTable(result api.GenericResult) string { ) } - if len(item.Attributes) > 0 { - ret = append(ret, - "", - " Attributes:", - base.WrapMap(4, maxLength, item.Attributes), - ) - } - return base.WrapForHelpText(ret) } diff --git a/internal/cmd/commands/hostsetscmd/plugin_funcs.go b/internal/cmd/commands/hostsetscmd/plugin_funcs.go new file mode 100644 index 000000000..5777357ce --- /dev/null +++ b/internal/cmd/commands/hostsetscmd/plugin_funcs.go @@ -0,0 +1,124 @@ +package hostsetscmd + +import ( + "fmt" + + "github.com/hashicorp/boundary/api/hostsets" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/libs/endpoint" + "github.com/hashicorp/go-secure-stdlib/parseutil" +) + +func init() { + extraPluginActionsFlagsMapFunc = extraPluginActionsFlagsMapFuncImpl + extraPluginFlagsFunc = extraPluginFlagsFuncImpl + extraPluginFlagsHandlingFunc = extraPluginFlagsHandlingFuncImpl +} + +type extraPluginCmdVars struct { + flagPreferredEndpoints []string + flagSyncInterval string +} + +func extraPluginActionsFlagsMapFuncImpl() map[string][]string { + return map[string][]string{ + "create": {"preferred-endpoint", "sync-interval"}, + "update": {"preferred-endpoint", "sync-interval"}, + } +} + +func (c *PluginCommand) extraPluginHelpFunc(helpMap map[string]func() string) string { + var helpStr string + switch c.Func { + case "create": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary host-sets create plugin [options] [args]", + "", + " Create a host set of a type provided by a plugin. Example:", + "", + ` $ boundary host-sets create plugin -plugin-id plg_1234567890 -name prodops -description "Plugin host-set for ProdOps"`, + "", + "", + }) + + case "update": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary host-sets update plugin [options] [args]", + "", + " Update a host set of a type provided by a plugin given its ID. Example:", + "", + ` $ boundary host-sets update plugin -id hsplg_1234567890 -name "devops" -description "Plugin host-set for DevOps"`, + "", + "", + }) + default: + helpStr = helpMap[c.Func]() + } + return helpStr + c.Flags().Help() +} + +func extraPluginFlagsFuncImpl(c *PluginCommand, set *base.FlagSets, f *base.FlagSet) { + fs := set.NewFlagSet("Plugin Host-Set Options") + + for _, name := range flagsPluginMap[c.Func] { + switch name { + case "preferred-endpoint": + fs.StringSliceVar(&base.StringSliceVar{ + Name: "preferred-endpoint", + Target: &c.flagPreferredEndpoints, + Usage: `An endpoint preference, specified by "cidr:" ` + + `or "dns:", specifying which IP address or DNS name out ` + + `of a host's available possibilities should be preferred. May be specified ` + + `multiple times, which will build up an in-order set of preferences. ` + + `If no preferences are specified, a value will be chosen from among all ` + + `available values using a built-in priority order. May not be valid ` + + `for all plugin types.`, + }) + case "sync-interval": + fs.StringVar(&base.StringVar{ + Name: "sync-interval", + Target: &c.flagSyncInterval, + Usage: `An interger number of seconds, or a string such as "400s", "5m", or "6h", ` + + "indicating the amount of time that should elapse between syncs of the host set. " + + "The interval will be applied to the end of the previous sync operation, not the start. " + + "Setting to any negative value will disable syncing for that host set; setting to null " + + "will cause the set to use Boundary's default. The default may change between releases.", + }) + } + } +} + +func extraPluginFlagsHandlingFuncImpl(c *PluginCommand, _ *base.FlagSets, opts *[]hostsets.Option) bool { + switch len(c.flagPreferredEndpoints) { + case 0: + case 1: + if c.flagPreferredEndpoints[0] == "null" { + *opts = append(*opts, hostsets.DefaultPreferredEndpoints()) + break + } + fallthrough + + default: + if _, err := endpoint.NewPreferencer(c.Context, endpoint.WithPreferenceOrder(c.flagPreferredEndpoints)); err != nil { + c.UI.Error(fmt.Sprintf("Unable to successfully validate preferred endpoints: %s", err)) + return false + } + *opts = append(*opts, hostsets.WithPreferredEndpoints(c.flagPreferredEndpoints)) + } + + switch c.flagSyncInterval { + case "": + case "null": + *opts = append(*opts, hostsets.DefaultSyncIntervalSeconds()) + + default: + interval, err := parseutil.ParseDurationSecond(c.flagSyncInterval) + if err != nil { + c.UI.Error(fmt.Sprintf("Unable to successfully parse given sync interval: %s", err)) + return false + } + *opts = append(*opts, hostsets.WithSyncIntervalSeconds(int32(interval.Seconds()))) + } + + return true +} diff --git a/internal/cmd/commands/hostsetscmd/plugin_hostsets.gen.go b/internal/cmd/commands/hostsetscmd/plugin_hostsets.gen.go new file mode 100644 index 000000000..8190cae04 --- /dev/null +++ b/internal/cmd/commands/hostsetscmd/plugin_hostsets.gen.go @@ -0,0 +1,259 @@ +// Code generated by "make cli"; DO NOT EDIT. +package hostsetscmd + +import ( + "errors" + "fmt" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/hostsets" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/go-secure-stdlib/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initPluginFlags() { + flagsOnce.Do(func() { + extraFlags := extraPluginActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsPluginMap[k] = append(flagsPluginMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*PluginCommand)(nil) + _ cli.CommandAutocomplete = (*PluginCommand)(nil) +) + +type PluginCommand struct { + *base.Command + + Func string + + plural string + + extraPluginCmdVars +} + +func (c *PluginCommand) AutocompleteArgs() complete.Predictor { + initPluginFlags() + return complete.PredictAnything +} + +func (c *PluginCommand) AutocompleteFlags() complete.Flags { + initPluginFlags() + return c.Flags().Completions() +} + +func (c *PluginCommand) Synopsis() string { + if extra := extraPluginSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "host set" + + synopsisStr = fmt.Sprintf("%s %s", "plugin-type", synopsisStr) + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *PluginCommand) Help() string { + initPluginFlags() + + var helpStr string + helpMap := common.HelpMap("host set") + + switch c.Func { + default: + + helpStr = c.extraPluginHelpFunc(helpMap) + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsPluginMap = map[string][]string{ + + "create": {"host-catalog-id", "name", "description", "attributes", "attr", "string-attr", "bool-attr", "num-attr"}, + + "update": {"id", "name", "description", "version", "attributes", "attr", "string-attr", "bool-attr", "num-attr"}, +} + +func (c *PluginCommand) Flags() *base.FlagSets { + if len(flagsPluginMap[c.Func]) == 0 { + return c.FlagSet(base.FlagSetNone) + } + + set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) + f := set.NewFlagSet("Command Options") + common.PopulateCommonFlags(c.Command, f, "plugin-type host set", flagsPluginMap, c.Func) + + f = set.NewFlagSet("Attribute Options") + common.PopulateAttributeFlags(c.Command, f, flagsPluginMap, c.Func) + + extraPluginFlagsFunc(c, set, f) + + return set +} + +func (c *PluginCommand) Run(args []string) int { + initPluginFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + } + + c.plural = "plugin-type host set" + switch c.Func { + case "list": + c.plural = "plugin-type host sets" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.PrintCliError(err) + return base.CommandUserError + } + + if strutil.StrListContains(flagsPluginMap[c.Func], "id") && c.FlagId == "" { + c.PrintCliError(errors.New("ID is required but not passed in via -id")) + return base.CommandUserError + } + + var opts []hostsets.Option + + if strutil.StrListContains(flagsPluginMap[c.Func], "host-catalog-id") { + switch c.Func { + case "create": + if c.FlagHostCatalogId == "" { + c.PrintCliError(errors.New("HostCatalog ID must be passed in via -host-catalog-id or BOUNDARY_HOST_CATALOG_ID")) + return base.CommandUserError + } + } + } + + client, err := c.Client() + if err != nil { + c.PrintCliError(fmt.Errorf("Error creating API client: %s", err.Error())) + return base.CommandCliError + } + hostsetsClient := hostsets.NewClient(client) + + switch c.FlagName { + case "": + case "null": + opts = append(opts, hostsets.DefaultName()) + default: + opts = append(opts, hostsets.WithName(c.FlagName)) + } + + switch c.FlagDescription { + case "": + case "null": + opts = append(opts, hostsets.DefaultDescription()) + default: + opts = append(opts, hostsets.WithDescription(c.FlagDescription)) + } + + if c.FlagFilter != "" { + opts = append(opts, hostsets.WithFilter(c.FlagFilter)) + } + + var version uint32 + + switch c.Func { + case "update": + switch c.FlagVersion { + case 0: + opts = append(opts, hostsets.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + } + + if err := common.HandleAttributeFlags( + c.Command, + "attr", + c.FlagAttributes, + c.FlagAttrs, + func() { + opts = append(opts, hostsets.DefaultAttributes()) + }, + func(in map[string]interface{}) { + opts = append(opts, hostsets.WithAttributes(in)) + }); err != nil { + c.PrintCliError(fmt.Errorf("Error evaluating attribute flags to: %s", err.Error())) + return base.CommandCliError + } + + if ok := extraPluginFlagsHandlingFunc(c, f, &opts); !ok { + return base.CommandUserError + } + + var result api.GenericResult + + switch c.Func { + + case "create": + result, err = hostsetsClient.Create(c.Context, c.FlagHostCatalogId, opts...) + + case "update": + result, err = hostsetsClient.Update(c.Context, c.FlagId, version, opts...) + + } + + result, err = executeExtraPluginActions(c, result, err, hostsetsClient, version, opts) + + if err != nil { + if apiErr := api.AsServerError(err); apiErr != nil { + var opts []base.Option + + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...) + return base.CommandApiError + } + c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) + return base.CommandCliError + } + + output, err := printCustomPluginActionOutput(c) + if err != nil { + c.PrintCliError(err) + return base.CommandUserError + } + if output { + return base.CommandSuccess + } + + switch c.Func { + } + + switch base.Format(c.UI) { + case "table": + c.UI.Output(printItemTable(result)) + + case "json": + if ok := c.PrintJsonItem(result); !ok { + return base.CommandCliError + } + } + + return base.CommandSuccess +} + +var ( + extraPluginActionsFlagsMapFunc = func() map[string][]string { return nil } + extraPluginSynopsisFunc = func(*PluginCommand) string { return "" } + extraPluginFlagsFunc = func(*PluginCommand, *base.FlagSets, *base.FlagSet) {} + extraPluginFlagsHandlingFunc = func(*PluginCommand, *base.FlagSets, *[]hostsets.Option) bool { return true } + executeExtraPluginActions = func(_ *PluginCommand, inResult api.GenericResult, inErr error, _ *hostsets.Client, _ uint32, _ []hostsets.Option) (api.GenericResult, error) { + return inResult, inErr + } + printCustomPluginActionOutput = func(*PluginCommand) (bool, error) { return false, nil } +) diff --git a/internal/cmd/config/config.go b/internal/cmd/config/config.go index b06b428c6..824ce0523 100644 --- a/internal/cmd/config/config.go +++ b/internal/cmd/config/config.go @@ -105,6 +105,9 @@ type Config struct { // Eventing configuration for the controller Eventing *event.EventerConfig `hcl:"events"` + + // Plugin-related options + Plugins Plugins `hcl:"plugins"` } type Controller struct { @@ -128,6 +131,12 @@ type Controller struct { // // TODO: This field is currently internal. StatusGracePeriodDuration time.Duration `hcl:"-"` + + // SchedulerRunJobInterval is the time interval between waking up the + // scheduler to run pending jobs. + // + // TODO: This field is currently internal. + SchedulerRunJobInterval time.Duration `hcl:"-"` } func (c *Controller) InitNameIfEmpty() (string, error) { @@ -186,6 +195,10 @@ type Database struct { MaxOpenConnections int `hcl:"max_open_connections"` } +type Plugins struct { + ExecutionDir string `hcl:"execution_dir"` +} + // DevWorker is a Config that is used for dev mode of Boundary // workers func DevWorker() (*Config, error) { diff --git a/internal/cmd/config/config_test.go b/internal/cmd/config/config_test.go index 7e73a4cdd..3f93c08c9 100644 --- a/internal/cmd/config/config_test.go +++ b/internal/cmd/config/config_test.go @@ -140,6 +140,19 @@ func TestDevController(t *testing.T) { assert.Empty(t, l0.CorsAllowedOrigins) assert.True(t, *l0.CorsDisableDefaultAllowedOriginValues) } + + // Test plugins block + { + // CORS disabled + conf := ` + plugins { + execution_dir = "/tmp/foobar" + } + ` + actual, err = Parse(conf) + assert.NoError(t, err) + assert.Equal(t, actual.Plugins.ExecutionDir, "/tmp/foobar") + } } func TestDevWorker(t *testing.T) { diff --git a/internal/cmd/gencli/input.go b/internal/cmd/gencli/input.go index 041274460..0c57d3827 100644 --- a/internal/cmd/gencli/input.go +++ b/internal/cmd/gencli/input.go @@ -265,6 +265,23 @@ var inputStructs = map[string][]*cmdInfo{ VersionedActions: []string{"update"}, NeedsSubtypeInCreate: true, }, + { + ResourceType: resource.HostCatalog.String(), + Pkg: "hostcatalogs", + StdActions: []string{"create", "update"}, + SubActionPrefix: "plugin", + SkipNormalHelp: true, + HasExtraHelpFunc: true, + HasId: true, + HasName: true, + HasDescription: true, + Container: "Scope", + IsPluginType: true, + VersionedActions: []string{"update"}, + NeedsSubtypeInCreate: true, + HasGenericAttributes: true, + HasGenericSecrets: true, + }, }, "hostsets": { { @@ -293,6 +310,21 @@ var inputStructs = map[string][]*cmdInfo{ HasDescription: true, VersionedActions: []string{"update"}, }, + { + ResourceType: resource.HostSet.String(), + Pkg: "hostsets", + StdActions: []string{"create", "update"}, + SubActionPrefix: "plugin", + HasExtraCommandVars: true, + SkipNormalHelp: true, + HasExtraHelpFunc: true, + HasId: true, + HasName: true, + Container: "HostCatalog", + HasDescription: true, + HasGenericAttributes: true, + VersionedActions: []string{"update"}, + }, }, "hosts": { { diff --git a/internal/db/option.go b/internal/db/option.go index ba963875d..981a01a79 100644 --- a/internal/db/option.go +++ b/internal/db/option.go @@ -48,6 +48,10 @@ type Options struct { withGormFormatter hclog.Logger withMaxOpenConnections int + // withDebug indicates that the given operation should invoke Gorm's debug + // mode + withDebug bool + withOnConflict *OnConflict withRowsAffected *int64 } @@ -189,6 +193,13 @@ func WithMaxOpenConnections(max int) Option { } } +// WithDebug specifies the given operation should invoke debug mode in Gorm +func WithDebug(with bool) Option { + return func(o *Options) { + o.withDebug = with + } +} + // WithOnConflict specifies an optional on conflict criteria which specify // alternative actions to take when an insert results in a unique constraint or // exclusion constraint error diff --git a/internal/db/option_test.go b/internal/db/option_test.go index f2cf9588b..24e7159c1 100644 --- a/internal/db/option_test.go +++ b/internal/db/option_test.go @@ -192,13 +192,22 @@ func Test_getOpts(t *testing.T) { testOpts.withMaxOpenConnections = 22 assert.Equal(opts, testOpts) }) + t.Run("WithDebug", func(t *testing.T) { + assert := assert.New(t) + // test default of false + opts := GetOpts() + testOpts := getDefaultOptions() + assert.Equal(opts, testOpts) + // try setting to true + opts = GetOpts(WithDebug(true)) + testOpts.withDebug = true + }) t.Run("WithOnConflict", func(t *testing.T) { assert := assert.New(t) // test default of false opts := GetOpts() testOpts := getDefaultOptions() assert.Equal(opts, testOpts) - columns := SetColumns([]string{"name", "description"}) columnValues := SetColumnValues(map[string]interface{}{"expiration": "NULL"}) testOnConflict := OnConflict{ diff --git a/internal/db/read_writer.go b/internal/db/read_writer.go index f8414832e..338339a68 100644 --- a/internal/db/read_writer.go +++ b/internal/db/read_writer.go @@ -88,14 +88,14 @@ type Writer interface { // rows updated or an error. Supported options: WithOplog. Update(ctx context.Context, i interface{}, fieldMaskPaths []string, setToNullPaths []string, opt ...Option) (int, error) - // Create an object in the db with options: WithOplog - // the caller is responsible for the transaction life cycle of the writer + // Create an object in the db with options: WithOplog, WithOnConflict. + // The caller is responsible for the transaction life cycle of the writer // and if an error is returned the caller must decide what to do with // the transaction, which almost always should be to rollback. Create(ctx context.Context, i interface{}, opt ...Option) error // CreateItems will create multiple items of the same type. - // Supported options: WithOplog and WithOplogMsgs. WithOplog and + // Supported options: WithOplog and WithOplogMsgs. WithOplog and // WithOplogMsgs may not be used together. WithLookup is not a // supported option. The caller is responsible for the transaction life // cycle of the writer and if an error is returned the caller must decide @@ -103,15 +103,15 @@ type Writer interface { // rollback. CreateItems(ctx context.Context, createItems []interface{}, opt ...Option) error - // Delete an object in the db with options: WithOplog - // the caller is responsible for the transaction life cycle of the writer + // Delete an object in the db with options: WithOplog, WithDebug. + // The caller is responsible for the transaction life cycle of the writer // and if an error is returned the caller must decide what to do with // the transaction, which almost always should be to rollback. Delete // returns the number of rows deleted or an error. Delete(ctx context.Context, i interface{}, opt ...Option) (int, error) // DeleteItems will delete multiple items of the same type. - // Supported options: WithOplog and WithOplogMsgs. WithOplog and + // Supported options: WithOplog and WithOplogMsgs. WithOplog and // WithOplogMsgs may not be used together. The caller is responsible for the // transaction life cycle of the writer and if an error is returned the // caller must decide what to do with the transaction, which almost always @@ -621,8 +621,8 @@ func (rw *Db) Update(ctx context.Context, i interface{}, fieldMaskPaths []string // Delete an object in the db with options: WithOplog, NewOplogMsg, WithWhere. // WithOplog will write an oplog entry for the delete. NewOplogMsg will return // in-memory oplog message. WithOplog and NewOplogMsg cannot be used together. -//WithWhere allows specifying an additional constraint on the operation in -//addition to the PKs. Delete returns the number of rows deleted and any errors. +// WithWhere allows specifying an additional constraint on the operation in +// addition to the PKs. Delete returns the number of rows deleted and any errors. func (rw *Db) Delete(ctx context.Context, i interface{}, opt ...Option) (int, error) { const op = "db.Delete" if rw.underlying == nil { @@ -667,6 +667,9 @@ func (rw *Db) Delete(ctx context.Context, i interface{}, opt ...Option) (int, er if opts.withWhereClause != "" { db = db.Where(opts.withWhereClause, opts.withWhereClauseArgs...) } + if opts.withDebug { + db = db.Debug() + } db = db.Delete(i) if db.Error != nil { return NoRowsAffected, errors.Wrap(ctx, db.Error, op, errors.WithoutEvent()) @@ -1135,7 +1138,7 @@ func (rw *Db) LookupWhere(ctx context.Context, resource interface{}, where strin // // Supports the WithLimit option. If WithLimit < 0, then unlimited results are returned. // If WithLimit == 0, then default limits are used for results. -// Supports the WithOrder option. +// Supports the WithOrder and WithDebug options. func (rw *Db) SearchWhere(ctx context.Context, resources interface{}, where string, args []interface{}, opt ...Option) error { const op = "db.SearchWhere" opts := GetOpts(opt...) @@ -1153,6 +1156,9 @@ func (rw *Db) SearchWhere(ctx context.Context, resources interface{}, where stri if opts.withOrder != "" { db = db.Order(opts.withOrder) } + if opts.withDebug { + db = db.Debug() + } // Perform limiting switch { case opts.WithLimit < 0: // any negative number signals unlimited results diff --git a/internal/db/schema/migrations/oss/postgres/20/01_domain_types.up.sql b/internal/db/schema/migrations/oss/postgres/20/01_domain_types.up.sql new file mode 100644 index 000000000..0686914e9 --- /dev/null +++ b/internal/db/schema/migrations/oss/postgres/20/01_domain_types.up.sql @@ -0,0 +1,18 @@ +begin; + +create domain wt_priority as int not null + constraint priority_must_be_greater_than_zero + check(value > 0); +comment on domain wt_priority is +'Represents a priority value which must not be null and must be greater than zero'; + +-- wt_dns_name defines a type for dns names +create domain wt_dns_name as text not null + constraint wt_dns_name_too_short + check (length(trim(value)) > 0) + constraint wt_dns_name_too_long + check (length(trim(value)) < 256); +comment on domain wt_dns_name is +'standard column for dns names'; + +commit; \ No newline at end of file diff --git a/internal/db/schema/migrations/oss/postgres/20/02_plugin.up.sql b/internal/db/schema/migrations/oss/postgres/20/02_plugin.up.sql new file mode 100644 index 000000000..9ac669031 --- /dev/null +++ b/internal/db/schema/migrations/oss/postgres/20/02_plugin.up.sql @@ -0,0 +1,78 @@ +begin; + + -- We are updating the plugin table here to have a scope id, since + -- subtypes of plugin should be scoped. We use 'global' as the default + -- just for the purpose of the migration since the scope cannot be null + -- and the one plugin that already exists (pi_system) can be globally + -- scoped. The immediately following statement removes this default. + -- We add a name so we can enforce that all plugins have a unique name + -- in a specific scope across all plugin subtypes. + alter table plugin + add column name wt_name, + add column scope_id wt_scope_id + not null + default 'global' + constraint iam_scope_global_fkey + references iam_scope_global(scope_id) + on delete cascade + on update cascade; + + alter table plugin + alter column scope_id drop default; + + -- Add constraints that enforce names are unique across all plugin subtypes + -- in a specific scope. + alter table plugin + add constraint plugin_scope_id_public_id_uq + unique (scope_id, public_id), + add constraint plugin_scope_id_name_uq + unique (scope_id, name); + + + -- insert, update, and delete plugin_subtypes are created since we are adding + -- subtyped plugins and we need to keep the base table plugin in sync with all + -- subtype tables. + create function insert_plugin_subtype() + returns trigger + as $$ + begin + insert into plugin + (public_id, scope_id, name) + values + (new.public_id, new.scope_id, new.name); + return new; + end; + $$ language plpgsql; + comment on function insert_plugin_subtype() is + 'insert_plugin_subtype() inserts sub type name into the base type plugin table'; + + create function update_plugin_subtype() + returns trigger + as $$ + begin + update plugin set name = new.name where public_id = new.public_id and new.name != name; + return new; + end; + $$ language plpgsql; + comment on function update_plugin_subtype() is + 'update_plugin_subtype() will update base plugin type name column with new values from sub type'; + + -- delete_plugin_subtype() is an after delete trigger function + -- for subtypes of plugin + create function delete_plugin_subtype() + returns trigger + as $$ + begin + delete from plugin + where public_id = old.public_id; + return null; -- result is ignored since this is an after trigger + end; + $$ language plpgsql; + comment on function delete_plugin_subtype is + 'delete_plugin_subtype() is an after trigger function for subytypes of plugin'; + + insert into oplog_ticket (name, version) + values + ('plugin', 1); + +commit; diff --git a/internal/db/schema/migrations/oss/postgres/20/03_host_plugin.up.sql b/internal/db/schema/migrations/oss/postgres/20/03_host_plugin.up.sql new file mode 100644 index 000000000..60c35ea24 --- /dev/null +++ b/internal/db/schema/migrations/oss/postgres/20/03_host_plugin.up.sql @@ -0,0 +1,72 @@ +begin; + +/* + ┌──────────────────┐ + │ plugin │ + ├──────────────────┤ + │public_id (pk) │ + │scope_id (fk) │ + │name │ + └──────────────────┘ + ┼ + ┼ + │ + ┼ + ┼ + ┌──────────────────┐ + │ plugin_host │ + ├──────────────────┤ + │public_id (pk) │ + │scope_id (fk) │ + │name │ + │description │ + │version │ + └──────────────────┘ +*/ + create table plugin_host ( + public_id wt_plugin_id primary key, + scope_id wt_scope_id not null + constraint iam_scope_global_fkey + references iam_scope_global(scope_id) + on delete cascade + on update cascade, + name wt_name, + description text, + create_time wt_timestamp, + update_time wt_timestamp, + version wt_version, + constraint plugin_fkey + foreign key (scope_id, public_id) + references plugin(scope_id, public_id) + on delete cascade + on update cascade, + constraint plugin_host_scope_id_name_uq + unique(scope_id, name) + ); + + create trigger update_version_column after update on plugin_host + for each row execute procedure update_version_column(); + + create trigger update_time_column before update on plugin_host + for each row execute procedure update_time_column(); + + create trigger default_create_time_column before insert on plugin_host + for each row execute procedure default_create_time(); + + create trigger immutable_columns before update on plugin_host + for each row execute procedure immutable_columns('public_id', 'create_time'); + + create trigger insert_plugin_subtype before insert on plugin_host + for each row execute procedure insert_plugin_subtype(); + + create trigger update_plugin_subtype before update on plugin_host + for each row execute procedure update_plugin_subtype(); + + create trigger delete_plugin_subtype after delete on plugin_host + for each row execute procedure delete_plugin_subtype(); + + insert into oplog_ticket (name, version) + values + ('plugin_host', 1); + +commit; diff --git a/internal/db/schema/migrations/oss/postgres/20/04_host.up.sql b/internal/db/schema/migrations/oss/postgres/20/04_host.up.sql new file mode 100644 index 000000000..2597f9700 --- /dev/null +++ b/internal/db/schema/migrations/oss/postgres/20/04_host.up.sql @@ -0,0 +1,61 @@ +begin; + + -- We are adding the name to the base host catalog type. This allows the db + -- to ensure that catalog names are unique in a scope across all subtypes. + alter table host_catalog + add column name wt_name; + + alter table host_catalog + add constraint host_catalog_scope_id_name_uq + unique (scope_id, name); + + -- Now that we've added the name column to the base type, we copy + -- the name from the static host catalog table into the base table. + update host_catalog + set name = st.name + from + static_host_catalog st + where + host_catalog.public_id = st.public_id and + st.name is not null; + + -- Replace the insert_host_catalog_subtype function defined in 0/20_host.up.sql + -- to include the name. + -- insert_host_catalog_subtype() is a before insert trigger + -- function for subtypes of host_catalog + create or replace function insert_host_catalog_subtype() + returns trigger + as $$ + begin + insert into host_catalog + (public_id, scope_id, name) + values + (new.public_id, new.scope_id, new.name); + return new; + end; + $$ language plpgsql; + comment on function insert_host_catalog_subtype() is + 'insert_host_catalog_subtype() inserts sub type name into the base type host catalog table'; + + -- Now that we are tracking the name, which is mutable, we need to also + -- update the base table when the subtype tables are updated. + -- update_host_catalog_subtype() is intended to be used as a before update + -- trigger for all host catalog sub types. The purpose is to ensure that the + -- base table for host catalog to contain the updated names for each host catalog + -- in order to enforce uniqueness across all host catalogs, regardless of subtype, + -- in a given scope. + create function update_host_catalog_subtype() + returns trigger + as $$ + begin + update host_catalog set name = new.name where public_id = new.public_id and new.name != name; + return new; + end; + $$ language plpgsql; + comment on function update_host_catalog_subtype() is + 'update_host_catalog_subtype() will update base host catalog type name column with new values from sub type'; + + create trigger update_host_catalog_subtype before update on static_host_catalog + for each row execute procedure update_host_catalog_subtype(); + +commit; diff --git a/internal/db/schema/migrations/oss/postgres/20/05_plugin_host.up.sql b/internal/db/schema/migrations/oss/postgres/20/05_plugin_host.up.sql new file mode 100644 index 000000000..a1f655389 --- /dev/null +++ b/internal/db/schema/migrations/oss/postgres/20/05_plugin_host.up.sql @@ -0,0 +1,389 @@ +begin; + +/* + ┌──────────────────┐ + │ plugin_host │ + ├──────────────────┤ + │public_id (pk) │ + │... │ + └──────────────────┘ + ┼ + ┼ + ○ + ╱│╲ + ┌─────────────────────┐ + │ host_plugin_catalog │ + ├─────────────────────┤ ┌───────────────────────────┐ + ┌────────────────┐ │public_id (pk) │ │host_plugin_catalog_secret │ + │host_catalog │ │plugin_id (fk) │ ├───────────────────────────┤ + ├────────────────┤ │scope_id (fk) │ │host_catalog_id (pk, fk) │ + │public_id │┼┼──○┼│name │┼┼──○┼│secret │ + │scope_id │ │description │ │key_id (fk) │ + └────────────────┘ │attributes │ └───────────────────────────┘ + ┼ └─────────────────────┘ + ┼ ┼ + │ ┼ + │ ○ + ○ ╱│╲ + ╱│╲ ┌────────────────────┐ + ┌────────────────┐ │ host_plugin_set │ + │ host_set │ ├────────────────────┤ + ├────────────────┤ │public_id (pk) │ + │public_id │ │host_catalog_id (fk)│ + │host_catalog_id │┼┼──○┼│name │ + └────────────────┘ │description │ + │attributes │ + └────────────────────┘ + +*/ + + create table host_plugin_catalog ( + public_id wt_public_id primary key, + scope_id wt_scope_id not null + constraint iam_scope_fkey + references iam_scope (public_id) + on delete cascade + on update cascade, + plugin_id wt_plugin_id not null + constraint plugin_host_fkey + references plugin_host (public_id) + on delete cascade + on update cascade, + name wt_name, + description text, + create_time wt_timestamp, + update_time wt_timestamp, + version wt_version, + attributes bytea not null, + constraint host_catalog_fkey + foreign key (scope_id, public_id) + references host_catalog (scope_id, public_id) + on delete cascade + on update cascade, + constraint host_plugin_catalog_scope_id_name_uq + unique(scope_id, name) + ); + + create trigger update_version_column after update on host_plugin_catalog + for each row execute procedure update_version_column(); + + create trigger update_time_column before update on host_plugin_catalog + for each row execute procedure update_time_column(); + + create trigger default_create_time_column before insert on host_plugin_catalog + for each row execute procedure default_create_time(); + + create trigger immutable_columns before update on host_plugin_catalog + for each row execute procedure immutable_columns('public_id', 'scope_id', 'plugin_id', 'create_time'); + + create trigger insert_host_catalog_subtype before insert on host_plugin_catalog + for each row execute procedure insert_host_catalog_subtype(); + + create trigger update_host_catalog_subtype before update on host_plugin_catalog + for each row execute procedure update_host_catalog_subtype(); + + create trigger delete_host_catalog_subtype after delete on host_plugin_catalog + for each row execute procedure delete_host_catalog_subtype(); + + create table host_plugin_catalog_secret ( + catalog_id wt_public_id primary key + constraint host_plugin_catalog_fkey + references host_plugin_catalog (public_id) + on delete cascade + on update cascade, + create_time wt_timestamp, + update_time wt_timestamp, + secret bytea not null -- encrypted value + constraint secret_must_not_be_empty + check(length(secret) > 0), + key_id wt_private_id not null + constraint kms_database_key_version_fkey + references kms_database_key_version (private_id) + on delete restrict + on update cascade + ); + + create trigger update_time_column before update on host_plugin_catalog_secret + for each row execute procedure update_time_column(); + + create trigger default_create_time_column before insert on host_plugin_catalog_secret + for each row execute procedure default_create_time(); + + create trigger immutable_columns before update on host_plugin_catalog_secret + for each row execute procedure immutable_columns('catalog_id', 'create_time'); + + create table host_plugin_set ( + public_id wt_public_id primary key, + catalog_id wt_public_id not null + constraint host_plugin_catalog_fkey + references host_plugin_catalog (public_id) + on delete cascade + on update cascade, + name wt_name, + description text, + create_time wt_timestamp, + update_time wt_timestamp, + last_sync_time wt_timestamp, + need_sync bool not null, + sync_interval_seconds int + constraint sync_interval_seconds_not_equal_zero + check(sync_interval_seconds != 0) + constraint sync_interval_seconds_not_less_then_negative_one + check(sync_interval_seconds >= -1), + version wt_version, + attributes bytea not null, + constraint host_plugin_set_catalog_id_name_uq + unique(catalog_id, name), + constraint host_set_fkey + foreign key (catalog_id, public_id) + references host_set (catalog_id, public_id) + on delete cascade + on update cascade, + constraint host_plugin_set_catalog_id_public_id_uq + unique(catalog_id, public_id) + ); + + create trigger update_version_column after update on host_plugin_set + for each row execute procedure update_version_column(); + + create trigger update_time_column before update on host_plugin_set + for each row execute procedure update_time_column(); + + create trigger default_create_time_column before insert on host_plugin_set + for each row execute procedure default_create_time(); + + create trigger immutable_columns before update on host_plugin_set + for each row execute procedure immutable_columns('public_id', 'catalog_id', 'create_time'); + + create trigger insert_host_set_subtype before insert on host_plugin_set + for each row execute procedure insert_host_set_subtype(); + + create trigger delete_host_set_subtype after delete on host_plugin_set + for each row execute procedure delete_host_set_subtype(); + +-- host_plugin_host captures plugin based host data. This is only written to + -- from the controller and is not mutable directly by actions from the end + -- user. + create table host_plugin_host ( + public_id wt_public_id primary key, + catalog_id wt_public_id not null + constraint host_plugin_catalog_fkey + references host_plugin_catalog (public_id) + on delete cascade + on update cascade, + external_id text not null + constraint external_id_must_not_be_empty + check(length(trim(external_id)) > 0), + name wt_name, + description text, + create_time wt_timestamp, + -- update_time is the last time the data was synced with what is provided + -- from the plugin. + update_time wt_timestamp, + constraint host_fkey + foreign key (catalog_id, public_id) + references host (catalog_id, public_id) + on delete cascade + on update cascade, + constraint host_plugin_host_catalog_id_external_id_uq + unique(catalog_id, external_id), + constraint host_plugin_host_catalog_id_public_id_uq + unique(catalog_id, public_id) + ); + + create trigger update_time_column before update on host_plugin_host + for each row execute procedure update_time_column(); + + create trigger default_create_time_column before insert on host_plugin_host + for each row execute procedure default_create_time(); + + create trigger immutable_columns before update on host_plugin_host + for each row execute procedure immutable_columns('public_id', 'catalog_id', 'external_id', 'create_time'); + + -- insert_host_plugin_host_subtype is intended as a before insert trigger on + -- host_plugin_host. Its purpose is to insert a base host for new plugin-based + -- hosts. It's a bit different than the standard trigger for this, because it + -- will have conflicting PKs and we just want to "do nothing" on those + -- conflicts, deferring the raising on an error to insert into the + -- host_plugin_host table. This allows the upsert-style workflow. + create or replace function + insert_host_plugin_host_subtype() + returns trigger + as $$ + begin + insert into host + (public_id, catalog_id) + values + (new.public_id, new.catalog_id) + on conflict do nothing; + + return new; + end; + $$ language plpgsql; + + create trigger insert_host_plugin_host_subtype before insert on host_plugin_host + for each row execute procedure insert_host_plugin_host_subtype(); + + create trigger delete_host_subtype after delete on host_plugin_host + for each row execute procedure delete_host_subtype(); + + -- host_ip_address contains the IP addresses associated with + -- a host, one per row. + create table host_ip_address ( + host_id wt_public_id + constraint host_fkey + references host(public_id) + on delete cascade + on update cascade, + address inet not null, + create_time wt_timestamp, + primary key (host_id, address) + ); + comment on table host_ip_address is + 'host_ip_address entries are ip addresses set on a host.'; + + create trigger default_create_time_column before insert on host_ip_address + for each row execute procedure default_create_time(); + + -- host_immutable_ip_address() ensures that ip addresses assigned to hosts are + -- immutable. + create function + host_immutable_ip_address() + returns trigger + as $$ + begin + raise exception 'host ip addresses are immutable'; + end; + $$ language plpgsql; + + create trigger immutable_ip_address + before update on host_ip_address + for each row execute procedure host_immutable_ip_address(); + + -- host_dns_name contains the DNS names associated with a host, one per row. + create table host_dns_name ( + host_id wt_public_id + constraint host_fkey + references host(public_id) + on delete cascade + on update cascade, + name wt_dns_name, + create_time wt_timestamp, + primary key (host_id, name) + ); + comment on table host_dns_name is + 'host_dns_name entries are dns names set on a host'; + + create trigger default_create_time_column before insert on host_dns_name + for each row execute procedure default_create_time(); + + -- host_immutable_dns_name() ensures that dns names assigned to hosts are + -- immutable. + create function + host_immutable_dns_name() + returns trigger + as $$ + begin + raise exception 'host dns names are immutable'; + end; + $$ language plpgsql; + + create trigger immutable_dns_name + before update on host_dns_name + for each row execute procedure host_immutable_dns_name(); + + create table host_plugin_set_member ( + host_id wt_public_id not null, + set_id wt_public_id not null, + catalog_id wt_public_id not null, + create_time wt_timestamp, + primary key(host_id, set_id), + constraint host_plugin_host_fkey + foreign key (catalog_id, host_id) + references host_plugin_host (catalog_id, public_id) + on delete cascade + on update cascade, + constraint host_plugin_set_fkey + foreign key (catalog_id, set_id) + references host_plugin_set (catalog_id, public_id) + on delete cascade + on update cascade + ); + comment on table host_plugin_set_member is + 'host_plugin_set_member entries are the membership relationships from plugin hosts in plugin sets.'; + + create trigger default_create_time_column before insert on host_plugin_set_member + for each row execute procedure default_create_time(); + + create trigger immutable_columns before update on host_plugin_set_member + for each row execute procedure immutable_columns('host_id', 'set_id', 'catalog_id', 'create_time'); + + create function insert_host_plugin_set_member() + returns trigger + as $$ + begin + select host_plugin_set.catalog_id + into new.catalog_id + from host_plugin_set + where host_plugin_set.public_id = new.set_id; + return new; + end; + $$ language plpgsql; + comment on function insert_host_plugin_set_member is + 'insert_host_plugin_set_member entries are the membership relationships from plugin hosts in plugin sets.'; + + create trigger insert_host_plugin_set_member before insert on host_plugin_set_member + for each row execute procedure insert_host_plugin_set_member(); + + create table host_set_preferred_endpoint ( + create_time wt_timestamp, + host_set_id wt_public_id not null + constraint host_set_fkey + references host_set(public_id) + on delete cascade + on update cascade, + priority wt_priority, + condition text not null + constraint condition_must_not_be_too_short + check(length(trim(condition)) > 4) -- minimum is 'dns:*' + constraint condition_must_not_be_too_long + check(length(trim(condition)) < 255) + constraint condition_has_valid_prefix + check( + left(trim(condition), 4) = 'dns:' + or + left(trim(condition), 5) = 'cidr:' + ) + constraint condition_does_not_contain_invalid_chars + check( + position('|' in trim(condition)) = 0 + and + position('=' in trim(condition)) = 0 + ), + primary key(host_set_id, priority), + constraint host_set_preferred_endpoint_host_set_id_condition_uq + unique(host_set_id, condition) + ); + + -- host_set_immutable_preferred_endpoint() ensures that endpoint conditions + -- assigned to host sets are immutable. + create function + host_set_immutable_preferred_endpoint() + returns trigger + as $$ + begin + raise exception 'preferred endpoints are immutable'; + end; + $$ language plpgsql; + + create trigger immutable_preferred_endpoint + before update on host_set_preferred_endpoint + for each row execute procedure host_set_immutable_preferred_endpoint(); + + insert into oplog_ticket (name, version) + values + ('host_plugin_catalog', 1), + ('host_plugin_set', 1), + ('host_plugin_host', 1); + +commit; diff --git a/internal/db/schema/migrations/oss/postgres/20/06_plugin_host_views.up.sql b/internal/db/schema/migrations/oss/postgres/20/06_plugin_host_views.up.sql new file mode 100644 index 000000000..f3e476457 --- /dev/null +++ b/internal/db/schema/migrations/oss/postgres/20/06_plugin_host_views.up.sql @@ -0,0 +1,85 @@ +begin; + +-- host_plugin_host_set_with_value_obj is useful for reading a plugin host set with its +-- associated value objects (preferred endpoints) as columns with delimited +-- values. The delimiter depends on the value objects (e.g. if they need +-- ordering). +create view host_plugin_host_set_with_value_obj as + select + hs.public_id, + hs.catalog_id, + hc.plugin_id, + hs.name, + hs.description, + hs.create_time, + hs.update_time, + hs.last_sync_time, + hs.need_sync, + hs.sync_interval_seconds, + hs.version, + hs.attributes, + -- the string_agg(..) column will be null if there are no associated value objects + string_agg(distinct concat_ws('=', hspe.priority, hspe.condition), '|') as preferred_endpoints, + string_agg(distinct hpsm.host_id, '|') as host_ids + from + host_plugin_set hs + join host_plugin_catalog hc on hs.catalog_id = hc.public_id + left outer join host_set_preferred_endpoint hspe on hs.public_id = hspe.host_set_id + left outer join host_plugin_set_member hpsm on hs.public_id = hpsm.set_id + group by hs.public_id, hc.plugin_id; +comment on view host_plugin_host_set_with_value_obj is +'host plugin host set with its associated value objects'; + +-- host_plugin_catalog_with_secret is useful for reading a plugin catalog with +-- its associated persisted data. +create view host_plugin_catalog_with_secret as +select + hc.public_id, + hc.scope_id, + hc.plugin_id, + hc.name, + hc.description, + hc.create_time, + hc.update_time, + hc.version, + hc.attributes, + hcs.secret, + hcs.key_id, + hcs.create_time as persisted_create_time, + hcs.update_time as persisted_update_time +from + host_plugin_catalog hc + left outer join host_plugin_catalog_secret hcs on hc.public_id = hcs.catalog_id; +comment on view host_plugin_catalog_with_secret is + 'host plugin catalog with its associated persisted data'; + +-- host_plugin_host_with_value_obj_and_set_memberships is useful for reading a +-- plugin host with its associated value objects (ip addresses, dns names) and +-- set membership as columns with delimited values. The delimiter depends on +-- the value objects (e.g. if they need ordering). +create view host_plugin_host_with_value_obj_and_set_memberships as +select + h.public_id, + h.catalog_id, + h.external_id, + hc.scope_id, + hc.plugin_id, + h.name, + h.description, + h.create_time, + h.update_time, + -- the string_agg(..) column will be null if there are no associated value objects + string_agg(distinct host(hip.address), '|') as ip_addresses, + string_agg(distinct hdns.name, '|') as dns_names, + string_agg(distinct hpsm.set_id, '|') as set_ids +from + host_plugin_host h + join host_plugin_catalog hc on h.catalog_id = hc.public_id + left outer join host_ip_address hip on h.public_id = hip.host_id + left outer join host_dns_name hdns on h.public_id = hdns.host_id + left outer join host_plugin_set_member hpsm on h.public_id = hpsm.host_id +group by h.public_id, hc.plugin_id, hc.scope_id; +comment on view host_plugin_host_with_value_obj_and_set_memberships is + 'host plugin host with its associated value objects'; + +commit; \ No newline at end of file diff --git a/internal/db/schema/migrations/oss/postgres/20/07_wh_session_dimensions.up.sql b/internal/db/schema/migrations/oss/postgres/20/07_wh_session_dimensions.up.sql new file mode 100644 index 000000000..cad5e897a --- /dev/null +++ b/internal/db/schema/migrations/oss/postgres/20/07_wh_session_dimensions.up.sql @@ -0,0 +1,85 @@ +begin; + +-- replaces view from 15/02_wh_rename_host_dimension_org.up.sql + +-- Updates whx_host_dimension_source to support plugin based host and related +-- resources. While this will return rows where there are host_sets where hosts +-- are not in the host sets, this view is only used when looking up a row by +-- host, set, and target so the records where hosts and host sets aren't +-- related beyond the shared catalog will be ignored. Retrieving the ip address +-- or dns name of plugin based hosts is not supported in this version of the +-- warehouse. +drop view whx_host_dimension_source; +create view whx_host_dimension_source as +select -- id is the first column in the target view + h.public_id as host_id, + case when sh.public_id is not null then 'static host' + when ph.public_id is not null then 'plugin host' + else 'Unknown' end as host_type, + case when sh.public_id is not null then coalesce(sh.name, 'None') + when ph.public_id is not null then coalesce(ph.name, 'None') + else 'Unknown' end as host_name, + case when sh.public_id is not null then coalesce(sh.description, 'None') + when ph.public_id is not null then coalesce(ph.description, 'None') + else 'Unknown' end as host_description, + + coalesce(sh.address, 'Unsupported') as host_address, + + hs.public_id as host_set_id, + case when shs.public_id is not null then 'static host set' + when phs.public_id is not null then 'plugin host set' + else 'Unknown' end as host_set_type, + case + when shs.public_id is not null then coalesce(shs.name, 'None') + when phs.public_id is not null then coalesce(phs.name, 'None') + else 'None' + end as host_set_name, + case + when shs.public_id is not null then coalesce(shs.description, 'None') + when phs.public_id is not null then coalesce(phs.description, 'None') + else 'None' + end as host_set_description, + hc.public_id as host_catalog_id, + case when shc.public_id is not null then 'static host catalog' + when phc.public_id is not null then 'plugin host catalog' + else 'Unknown' end as host_catalog_type, + case + when shc.public_id is not null then coalesce(shc.name, 'None') + when phc.public_id is not null then coalesce(phc.name, 'None') + else 'None' + end as host_catalog_name, + case + when shc.public_id is not null then coalesce(shc.description, 'None') + when phc.public_id is not null then coalesce(phc.description, 'None') + else 'None' + end as host_catalog_description, + t.public_id as target_id, + 'tcp target' as target_type, + coalesce(t.name, 'None') as target_name, + coalesce(t.description, 'None') as target_description, + coalesce(t.default_port, 0) as target_default_port_number, + t.session_max_seconds as target_session_max_seconds, + t.session_connection_limit as target_session_connection_limit, + p.public_id as project_id, + coalesce(p.name, 'None') as project_name, + coalesce(p.description, 'None') as project_description, + o.public_id as organization_id, + coalesce(o.name, 'None') as organization_name, + coalesce(o.description, 'None') as organization_description + from host as h + join host_catalog as hc on h.catalog_id = hc.public_id + join host_set as hs on h.catalog_id = hs.catalog_id + join target_host_set as ts on hs.public_id = ts.host_set_id + join target_tcp as t on ts.target_id = t.public_id + join iam_scope as p on t.scope_id = p.public_id and p.type = 'project' + join iam_scope as o on p.parent_id = o.public_id and o.type = 'org' + + left join static_host as sh on sh.public_id = h.public_id + left join host_plugin_host as ph on ph.public_id = h.public_id + left join static_host_catalog as shc on shc.public_id = hc.public_id + left join host_plugin_catalog as phc on phc.public_id = hc.public_id + left join static_host_set as shs on shs.public_id = hs.public_id + left join host_plugin_set as phs on phs.public_id = hs.public_id +; + +commit; diff --git a/internal/db/schema/migrations/oss/postgres_17_05_test.go b/internal/db/schema/migrations/oss/postgres_17_05_test.go new file mode 100644 index 000000000..662270538 --- /dev/null +++ b/internal/db/schema/migrations/oss/postgres_17_05_test.go @@ -0,0 +1,166 @@ +package oss_test + +import ( + "context" + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/host" + "github.com/hashicorp/boundary/internal/host/static" + "github.com/hashicorp/boundary/internal/iam" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_PreferredEndpointTable(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + rootWrapper := db.TestWrapper(t) + _, proj := iam.TestScopes(t, iam.TestRepo(t, conn, rootWrapper)) + hc := static.TestCatalogs(t, conn, proj.PublicId, 1)[0] + hs := static.TestSets(t, conn, hc.PublicId, 1)[0] + + type testCondition struct { + condition string + priority uint32 + } + insertTests := []struct { + testName string + hostSetId string + conditions []testCondition + wantErrContains string + }{ + { + testName: "invalid host set id", + hostSetId: "hsst_1234567890", + conditions: []testCondition{ + { + priority: 1, + condition: "dns:abcd", + }, + { + priority: 2, + condition: "cidr:1.2.3.4", + }, + }, + wantErrContains: "host_set_fkey", + }, + { + testName: "invalid condition prefix", + hostSetId: hs.PublicId, + conditions: []testCondition{ + { + priority: 1, + condition: "dsn:abcd", + }, + }, + wantErrContains: "condition_has_valid_prefix", + }, + { + testName: "invalid condition length", + hostSetId: hs.PublicId, + conditions: []testCondition{ + { + priority: 1, + condition: "dns:", + }, + }, + wantErrContains: "condition_must_not_be_too_short", + }, + { + testName: "invalid priority", + hostSetId: hs.PublicId, + conditions: []testCondition{ + { + priority: 0, + condition: "dns:abcd", + }, + }, + wantErrContains: "priority_must_be_greater_than_zero", + }, + { + testName: "duplicate priority", + hostSetId: hs.PublicId, + conditions: []testCondition{ + { + priority: 1, + condition: "dns:abcd", + }, + { + priority: 1, + condition: "cidr:1.2.3.4", + }, + }, + wantErrContains: "host_set_preferred_endpoint_pkey", + }, + { + testName: "invalid char 1", + hostSetId: hs.PublicId, + conditions: []testCondition{ + { + priority: 1, + condition: "dns:ab|cd", + }, + { + priority: 1, + condition: "cidr:1.2.3.4", + }, + }, + wantErrContains: "condition_does_not_contain_invalid_chars", + }, + { + testName: "invalid char 2", + hostSetId: hs.PublicId, + conditions: []testCondition{ + { + priority: 1, + condition: "dns:abcd", + }, + { + priority: 1, + condition: "cidr:1.2=.3.4", + }, + }, + wantErrContains: "condition_does_not_contain_invalid_chars", + }, + { + testName: "valid", + hostSetId: hs.PublicId, + conditions: []testCondition{ + { + priority: 1, + condition: "dns:abcd", + }, + { + priority: 2, + condition: "cidr:1.2.3.4", + }, + }, + }, + } + for _, tt := range insertTests { + t.Run("insert: "+tt.testName, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + _, err := rw.Exec(ctx, "delete from host_set_preferred_endpoint where host_set_id = ?", []interface{}{tt.hostSetId}) + require.NoError(err) + + // Add items to insert + var items []interface{} + for _, cond := range tt.conditions { + ep := host.AllocPreferredEndpoint() + ep.HostSetId = tt.hostSetId + ep.Condition = cond.condition + ep.Priority = cond.priority + items = append(items, ep) + } + err = rw.CreateItems(ctx, items) + if tt.wantErrContains != "" { + require.Error(err) + assert.Contains(err.Error(), tt.wantErrContains) + return + } + require.NoError(err) + }) + } +} diff --git a/internal/db/sqltest/initdb.d/03_widgets_persona.sql b/internal/db/sqltest/initdb.d/03_widgets_persona.sql index 7ddf2ee73..791f011e2 100644 --- a/internal/db/sqltest/initdb.d/03_widgets_persona.sql +++ b/internal/db/sqltest/initdb.d/03_widgets_persona.sql @@ -236,6 +236,44 @@ begin; static_host_set as s where h.catalog_id = s.catalog_id and h.address like '%.widget'; + + insert into plugin_host + (scope_id, public_id, name) + values + ('global', 'plg___wb-hplg', 'Short Host Plugin'); + + insert into host_plugin_catalog + (scope_id, public_id, plugin_id, name, attributes) + values + ('p____bwidget', 'c___wb-plghcl', 'plg___wb-hplg', 'Big Widget Plugin Catalog', ''), + ('p____swidget', 'c___ws-plghcl', 'plg___wb-hplg', 'Small Widget Plugin Catalog', ''); + + insert into host_plugin_host + (catalog_id, public_id, external_id) + values + ('c___wb-plghcl', 'h_____wb__01-plgh', '1 big widget'), + ('c___wb-plghcl', 'h_____wb__02-plgh', '2 big widget'), + ('c___wb-plghcl', 'h_____wb__03-plgh', '3 big widget'), + + ('c___ws-plghcl', 'h_____ws__01-plgh', '1 small widget'), + ('c___ws-plghcl', 'h_____ws__02-plgh', '2 small widget'), + ('c___ws-plghcl', 'h_____ws__03-plgh', '3 small widget'); + + insert into host_plugin_set + (catalog_id, public_id, name, attributes, need_sync) + values + ('c___wb-plghcl', 's___1wb-plghs', 'Big Widget Plugin Set 1', '', false), + ('c___wb-plghcl', 's___2wb-plghs', 'Big Widget Plugin Set 2', '', false), + ('c___ws-plghcl', 's___1ws-plghs', 'Small Widget Plugin Set 1', '', false), + ('c___ws-plghcl', 's___2ws-plghs', 'Small Widget Plugin Set 2', '', false); + + insert + into host_plugin_set_member + ( host_id, set_id, catalog_id) + select h.public_id, s.public_id, s.catalog_id + from host_plugin_host as h, + host_plugin_set as s + where h.catalog_id = s.catalog_id; end; $$ language plpgsql; @@ -257,7 +295,11 @@ begin; ('t_________wb', 's___1wb-sths'), ('t_________wb', 's___2wb-sths'), ('t_________ws', 's___1ws-sths'), - ('t_________ws', 's___2ws-sths'); + ('t_________ws', 's___2ws-sths'), + ('t_________wb', 's___1wb-plghs'), + ('t_________wb', 's___2wb-plghs'), + ('t_________ws', 's___1ws-plghs'), + ('t_________ws', 's___2ws-plghs'); end; $$ language plpgsql; diff --git a/internal/db/sqltest/tests/wh/host_dimension_views/source.sql b/internal/db/sqltest/tests/wh/host_dimension_views/source.sql index 5c9bc77ae..3334d87fb 100644 --- a/internal/db/sqltest/tests/wh/host_dimension_views/source.sql +++ b/internal/db/sqltest/tests/wh/host_dimension_views/source.sql @@ -1,9 +1,9 @@ -- source tests the whx_host_dimension_source view. begin; - select plan(1); + select plan(2); select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets'); - -- auth_password_account + -- Static hosts select is(s.*, row( 'h_____wb__01', 'static host', 'None', 'None', '1.big.widget', 's___2wb-sths', 'static host set', 'Big Widget Static Set 2', 'None', @@ -17,6 +17,20 @@ begin; and s.host_set_id = 's___2wb-sths' and s.target_id = 't_________wb'; + -- Plugin based hosts + select is(s.*, row( + 'h_____wb__01-plgh', 'plugin host', 'None', 'None', 'Unsupported', + 's___2wb-plghs', 'plugin host set', 'Big Widget Plugin Set 2', 'None', + 'c___wb-plghcl', 'plugin host catalog', 'Big Widget Plugin Catalog', 'None', + 't_________wb', 'tcp target', 'Big Widget Target', 'None', 0, 28800, 1, + 'p____bwidget', 'Big Widget Factory', 'None', + 'o_____widget', 'Widget Inc', 'None' + )::whx_host_dimension_source) + from whx_host_dimension_source as s + where s.host_id = 'h_____wb__01-plgh' + and s.host_set_id = 's___2wb-plghs' + and s.target_id = 't_________wb'; + select * from finish(); rollback; diff --git a/internal/errors/code.go b/internal/errors/code.go index 02ea814ec..1c4a75846 100644 --- a/internal/errors/code.go +++ b/internal/errors/code.go @@ -89,6 +89,10 @@ const ( // reasons related to the state of the DDL and/or inputs (such as we're // already in the right state and don't want to end up writing oplogs). GracefullyAborted Code = 1106 + // UnexpectedRowsAffected indicates that an action expected to operate on a + // specific number of records returned a different count, e.g. if you + // expected to delete three items and only one was deleted. + UnexpectedRowsAffected Code = 1107 // Migration setup errors are codes 2000-2999 MigrationIntegrity Code = 2000 // MigrationIntegrity represents an error with the generated migration related code diff --git a/internal/errors/code_test.go b/internal/errors/code_test.go index 0b491f66c..78326f4de 100644 --- a/internal/errors/code_test.go +++ b/internal/errors/code_test.go @@ -292,6 +292,11 @@ func TestCode_Both_String_Info(t *testing.T) { c: GracefullyAborted, want: GracefullyAborted, }, + { + name: "UnexpectedRowsAffected", + c: UnexpectedRowsAffected, + want: UnexpectedRowsAffected, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/errors/info.go b/internal/errors/info.go index c9ec7a77c..40004249d 100644 --- a/internal/errors/info.go +++ b/internal/errors/info.go @@ -236,4 +236,8 @@ var errorCodeInfo = map[Code]Info{ Message: "purposefully aborted without error", Kind: Other, }, + UnexpectedRowsAffected: { + Message: "unexpected number of rows affected", + Kind: Integrity, + }, } diff --git a/internal/gen/controller.swagger.json b/internal/gen/controller.swagger.json index cfa71657b..8723b2ace 100644 --- a/internal/gen/controller.swagger.json +++ b/internal/gen/controller.swagger.json @@ -1244,6 +1244,13 @@ "schema": { "$ref": "#/definitions/controller.api.resources.hostcatalogs.v1.HostCatalog" } + }, + { + "name": "plugin_name", + "description": "As an alternative to providing the plugin id in the provided HostCatalog,\nthis field can be used to lookup the plugin using its name.", + "in": "query", + "required": false, + "type": "string" } ], "tags": [ @@ -4093,6 +4100,15 @@ "description": "Output only. Scope information for this resource.", "readOnly": true }, + "plugin_id": { + "type": "string", + "description": "The ID of the plugin of which this catalog is created." + }, + "plugin": { + "$ref": "#/definitions/controller.api.resources.plugins.v1.PluginInfo", + "description": "Output only. Plugin information for this resource.", + "readOnly": true + }, "name": { "type": "string", "description": "Optional name for identification purposes." @@ -4126,6 +4142,10 @@ "type": "object", "description": "Attributes specific to the catalog type." }, + "secrets": { + "type": "object", + "description": "Secrets specific to the catalog type. These are never output." + }, "authorized_actions": { "type": "array", "items": { @@ -4165,6 +4185,11 @@ "description": "Output only. Scope information for this resource.", "readOnly": true }, + "plugin": { + "$ref": "#/definitions/controller.api.resources.plugins.v1.PluginInfo", + "description": "Output only. Plugin information for this resource.", + "readOnly": true + }, "name": { "type": "string", "description": "Optional name for identification purposes." @@ -4206,6 +4231,22 @@ "type": "object", "description": "The attributes that are applicable to the specific Host type." }, + "ip_addresses": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Output only. The list of ip addresses associated with this host.", + "readOnly": true + }, + "dns_names": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Output only. The list of dns addresses associated with this host.", + "readOnly": true + }, "authorized_actions": { "type": "array", "items": { @@ -4234,6 +4275,11 @@ "description": "Output only. Scope information for this resource.", "readOnly": true }, + "plugin": { + "$ref": "#/definitions/controller.api.resources.plugins.v1.PluginInfo", + "description": "Output only. Plugin information for this resource.", + "readOnly": true + }, "name": { "type": "string", "description": "Optional name for identification purposes." @@ -4271,6 +4317,18 @@ "description": "Output only. A list of Hosts in this Host Set.", "readOnly": true }, + "preferred_endpoints": { + "type": "array", + "items": { + "type": "string" + }, + "description": "multiple possible endpoints for a host. Preferences are specified by\n\"cidr:\u003cvalid IPv4/6 CIDR\u003e\" or \"dns:\u003cglobbed name\u003e\", specifying which IP\naddress or DNS name out of a host's available possibilities should be\npreferred. If no preferences are specified, a value will be chosen from\namong all avialable values using a built-in priority order. May not be\nvalid for all plugin types." + }, + "sync_interval_seconds": { + "type": "integer", + "format": "int32", + "description": "An interger number of seconds indicating the amount of time that should\nelapse between syncs of the host set. The interval will be applied to the\nend of the previous sync operation, not the start. Setting to any\nnegative value will disable syncing for that host set; setting to zero\nwill cause the set to use Boundary's default. The default may change\nbetween releases. May not be valid for all plugin types." + }, "attributes": { "type": "object", "description": "The attributes that are applicable for the specific Host Set type." @@ -4355,6 +4413,26 @@ }, "title": "ManagedGroup contains all fields related to an ManagedGroup resource" }, + "controller.api.resources.plugins.v1.PluginInfo": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Output only. The ID of the Plugin.", + "readOnly": true + }, + "name": { + "type": "string", + "description": "Output only. The name of the plugin resource in boundary, if any.", + "readOnly": true + }, + "description": { + "type": "string", + "description": "Output only. The description of the plugin in boundary, if any.", + "readOnly": true + } + } + }, "controller.api.resources.roles.v1.Grant": { "type": "object", "properties": { diff --git a/internal/gen/controller/api/services/host_catalog_service.pb.go b/internal/gen/controller/api/services/host_catalog_service.pb.go index fb70dea13..26edfda4f 100644 --- a/internal/gen/controller/api/services/host_catalog_service.pb.go +++ b/internal/gen/controller/api/services/host_catalog_service.pb.go @@ -234,6 +234,9 @@ type CreateHostCatalogRequest struct { unknownFields protoimpl.UnknownFields Item *hostcatalogs.HostCatalog `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` + // As an alternative to providing the plugin id in the provided HostCatalog, + // this field can be used to lookup the plugin using its name. + PluginName string `protobuf:"bytes,2,opt,name=plugin_name,proto3" json:"plugin_name,omitempty"` } func (x *CreateHostCatalogRequest) Reset() { @@ -275,6 +278,13 @@ func (x *CreateHostCatalogRequest) GetItem() *hostcatalogs.HostCatalog { return nil } +func (x *CreateHostCatalogRequest) GetPluginName() string { + if x != nil { + return x.PluginName + } + return "" +} + type CreateHostCatalogResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -566,111 +576,113 @@ var file_controller_api_services_v1_host_catalog_service_proto_rawDesc = []byte{ 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x05, 0x69, - 0x74, 0x65, 0x6d, 0x73, 0x22, 0x65, 0x0a, 0x18, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, 0x6f, - 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x49, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, - 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x63, 0x61, - 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, - 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x78, 0x0a, 0x19, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x12, 0x49, 0x0a, 0x04, 0x69, 0x74, - 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x74, 0x65, 0x6d, 0x73, 0x22, 0x87, 0x01, 0x0a, 0x18, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, + 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x49, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x63, + 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, + 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x20, 0x0a, 0x0b, + 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x78, + 0x0a, 0x19, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, + 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, + 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x12, 0x49, 0x0a, + 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x63, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x63, 0x61, 0x74, 0x61, 0x6c, + 0x6f, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, + 0x6f, 0x67, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0xb2, 0x01, 0x0a, 0x18, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x49, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x68, + 0x6f, 0x73, 0x74, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x48, + 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, + 0x12, 0x3b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, + 0x6b, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x73, 0x6b, 0x22, 0x66, 0x0a, + 0x19, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, + 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x04, 0x69, 0x74, + 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, - 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0xb2, 0x01, 0x0a, 0x18, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, - 0x69, 0x64, 0x12, 0x49, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x68, 0x6f, 0x73, 0x74, - 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, - 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x3b, 0x0a, - 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x52, 0x0a, - 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x73, 0x6b, 0x22, 0x66, 0x0a, 0x19, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, - 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, - 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x76, 0x31, - 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x04, 0x69, 0x74, - 0x65, 0x6d, 0x22, 0x2a, 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, - 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, - 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x1b, - 0x0a, 0x19, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, - 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xde, 0x07, 0x0a, 0x12, - 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x12, 0xbd, 0x01, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, - 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x12, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, - 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, - 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x2a, 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x48, + 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, + 0x64, 0x22, 0x1b, 0x0a, 0x19, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x43, + 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xde, + 0x07, 0x0a, 0x12, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xbd, 0x01, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, + 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x12, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, - 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x44, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x1e, 0x12, 0x16, 0x2f, 0x76, 0x31, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x63, - 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x62, 0x04, 0x69, 0x74, - 0x65, 0x6d, 0x92, 0x41, 0x1d, 0x12, 0x1b, 0x47, 0x65, 0x74, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, - 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x20, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, - 0x67, 0x2e, 0x12, 0xba, 0x01, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x43, - 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x12, 0x33, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, - 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, - 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x63, - 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x6f, - 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x3b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, 0x76, 0x31, 0x2f, - 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x92, 0x41, 0x1f, - 0x12, 0x1d, 0x47, 0x65, 0x74, 0x73, 0x20, 0x61, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x6f, 0x66, - 0x20, 0x48, 0x6f, 0x73, 0x74, 0x20, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x12, - 0xc2, 0x01, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, - 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x12, 0x34, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, - 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, - 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, - 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x63, 0x6f, + 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, - 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x40, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x22, 0x11, 0x2f, 0x76, 0x31, 0x2f, - 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x3a, 0x04, 0x69, - 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x18, 0x12, 0x16, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x20, 0x43, 0x61, 0x74, - 0x61, 0x6c, 0x6f, 0x67, 0x12, 0xc7, 0x01, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, - 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x12, 0x34, 0x2e, 0x63, 0x6f, 0x6e, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, + 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x44, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x12, 0x16, 0x2f, 0x76, 0x31, 0x2f, 0x68, 0x6f, 0x73, + 0x74, 0x2d, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x62, + 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x1d, 0x12, 0x1b, 0x47, 0x65, 0x74, 0x73, 0x20, 0x61, + 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x20, 0x43, 0x61, 0x74, + 0x61, 0x6c, 0x6f, 0x67, 0x2e, 0x12, 0xba, 0x01, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x6f, + 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x12, 0x33, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x6f, - 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x45, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x24, 0x32, - 0x16, 0x2f, 0x76, 0x31, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, - 0x67, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, - 0x74, 0x65, 0x6d, 0x92, 0x41, 0x18, 0x12, 0x16, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x20, - 0x61, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x20, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x12, 0xbb, - 0x01, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, - 0x61, 0x6c, 0x6f, 0x67, 0x12, 0x34, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, - 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, - 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, - 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x63, 0x6f, 0x6e, - 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x6f, - 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x39, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x2a, 0x16, 0x2f, 0x76, 0x31, 0x2f, 0x68, - 0x6f, 0x73, 0x74, 0x2d, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x7b, 0x69, 0x64, - 0x7d, 0x92, 0x41, 0x18, 0x12, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, - 0x48, 0x6f, 0x73, 0x74, 0x20, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x42, 0x4d, 0x5a, 0x4b, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, - 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, - 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, - 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x73, 0x3b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, + 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x34, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, + 0x76, 0x31, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, + 0x92, 0x41, 0x1f, 0x12, 0x1d, 0x47, 0x65, 0x74, 0x73, 0x20, 0x61, 0x20, 0x6c, 0x69, 0x73, 0x74, + 0x20, 0x6f, 0x66, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x20, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, + 0x73, 0x2e, 0x12, 0xc2, 0x01, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, + 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x12, 0x34, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, + 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, + 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x40, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x22, 0x11, 0x2f, + 0x76, 0x31, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, + 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x18, 0x12, + 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x20, + 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x12, 0xc7, 0x01, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x12, 0x34, 0x2e, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, + 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, + 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x45, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x24, 0x32, 0x16, 0x2f, 0x76, 0x31, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x63, 0x61, 0x74, + 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, + 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x18, 0x12, 0x16, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x73, 0x20, 0x61, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x20, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, + 0x67, 0x12, 0xbb, 0x01, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, + 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x12, 0x34, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x43, + 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x39, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x2a, 0x16, 0x2f, 0x76, + 0x31, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2f, + 0x7b, 0x69, 0x64, 0x7d, 0x92, 0x41, 0x18, 0x12, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x73, + 0x20, 0x61, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x20, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x42, + 0x4d, 0x5a, 0x4b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, + 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, + 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x3b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/internal/gen/controller/api/services/host_catalog_service.pb.gw.go b/internal/gen/controller/api/services/host_catalog_service.pb.gw.go index e36256dae..e76c2dbc8 100644 --- a/internal/gen/controller/api/services/host_catalog_service.pb.gw.go +++ b/internal/gen/controller/api/services/host_catalog_service.pb.gw.go @@ -119,6 +119,10 @@ func local_request_HostCatalogService_ListHostCatalogs_0(ctx context.Context, ma } +var ( + filter_HostCatalogService_CreateHostCatalog_0 = &utilities.DoubleArray{Encoding: map[string]int{"item": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} +) + func request_HostCatalogService_CreateHostCatalog_0(ctx context.Context, marshaler runtime.Marshaler, client HostCatalogServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq CreateHostCatalogRequest var metadata runtime.ServerMetadata @@ -131,6 +135,13 @@ func request_HostCatalogService_CreateHostCatalog_0(ctx context.Context, marshal return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HostCatalogService_CreateHostCatalog_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := client.CreateHostCatalog(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err @@ -148,6 +159,13 @@ func local_request_HostCatalogService_CreateHostCatalog_0(ctx context.Context, m return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HostCatalogService_CreateHostCatalog_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.CreateHostCatalog(ctx, &protoReq) return msg, metadata, err diff --git a/internal/host/endpoint.go b/internal/host/endpoint.go new file mode 100644 index 000000000..50f5792fe --- /dev/null +++ b/internal/host/endpoint.go @@ -0,0 +1,9 @@ +package host + +// Endpoint is a struct which identifies an address provided by a host and +// selected as the priority address by the specified host set. +type Endpoint struct { + HostId string + SetId string + Address string +} diff --git a/internal/host/host.go b/internal/host/host.go new file mode 100644 index 000000000..1c54995fb --- /dev/null +++ b/internal/host/host.go @@ -0,0 +1,25 @@ +package host + +import "github.com/hashicorp/boundary/internal/boundary" + +// Catalog contains the common methods across all the different types of host +// catalogs. +type Catalog interface { + boundary.Resource + GetScopeId() string +} + +// Set contains the common methods across all the different types of host sets. +type Set interface { + boundary.Resource + GetCatalogId() string +} + +// Host contains the common methods across all the different types of hosts. +type Host interface { + boundary.Resource + GetCatalogId() string + GetAddress() string + GetIpAddresses() []string + GetDnsNames() []string +} diff --git a/internal/host/host_dns_address.go b/internal/host/host_dns_address.go new file mode 100644 index 000000000..3c7de2df5 --- /dev/null +++ b/internal/host/host_dns_address.go @@ -0,0 +1,72 @@ +package host + +import ( + "context" + + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/host/store" + "google.golang.org/protobuf/proto" +) + +// NOTE: Tests for this are in the plugin subtype directory in host_address_test.go + +const defaultDnsNameTableName = "host_dns_name" + +type DnsName struct { + *store.DnsName + tableName string `gorm:"-"` +} + +func NewDnsName(ctx context.Context, hostId string, name string) (*DnsName, error) { + const op = "host.NewDnsName" + dn := &DnsName{ + DnsName: &store.DnsName{ + HostId: hostId, + Name: name, + }, + } + if err := dn.validate(ctx, op); err != nil { + return nil, err + } + return dn, nil +} + +// validate the host dns name. On success, it will return nil. +func (dn *DnsName) validate(ctx context.Context, caller errors.Op) error { + if dn.HostId == "" { + return errors.New(ctx, errors.InvalidParameter, caller, "missing host id") + } + if dn.Name == "" { + return errors.New(ctx, errors.InvalidParameter, caller, "missing dns name") + } + + return nil +} + +// allocDnsName make an empty one in memory. +func allocDnsName() DnsName { + return DnsName{ + DnsName: &store.DnsName{}, + } +} + +// Clone an DnsName +func (c *DnsName) Clone() *DnsName { + cp := proto.Clone(c.DnsName) + return &DnsName{ + DnsName: cp.(*store.DnsName), + } +} + +// TableName returns the table name. +func (c *DnsName) TableName() string { + if c.tableName != "" { + return c.tableName + } + return defaultDnsNameTableName +} + +// SetTableName sets the table name. +func (c *DnsName) SetTableName(n string) { + c.tableName = n +} diff --git a/internal/host/host_ip_address.go b/internal/host/host_ip_address.go new file mode 100644 index 000000000..0f7ba6b15 --- /dev/null +++ b/internal/host/host_ip_address.go @@ -0,0 +1,77 @@ +package host + +import ( + "context" + "net" + + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/host/store" + "google.golang.org/protobuf/proto" +) + +// NOTE: Tests for this are in the plugin subtype directory in host_address_test.go + +const defaultIpAddressTableName = "host_ip_address" + +type IpAddress struct { + *store.IpAddress + tableName string `gorm:"-"` +} + +func NewIpAddress(ctx context.Context, hostId string, address string) (*IpAddress, error) { + const op = "host.NewIpAddress" + + ia := &IpAddress{ + IpAddress: &store.IpAddress{ + HostId: hostId, + Address: address, + }, + } + if err := ia.validate(ctx, op); err != nil { + return nil, err + } + return ia, nil +} + +// validate the host ip address. On success, it will return nil. +func (ia *IpAddress) validate(ctx context.Context, caller errors.Op) error { + if ia.HostId == "" { + return errors.New(ctx, errors.InvalidParameter, caller, "missing host id") + } + if ia.Address == "" { + return errors.New(ctx, errors.InvalidParameter, caller, "missing ip address") + } + if ip := net.ParseIP(ia.Address); ip == nil { + return errors.New(ctx, errors.InvalidParameter, caller, "given address is not an ip address") + } + + return nil +} + +// allocIpAddress make an empty one in memory. +func allocIpAddress() IpAddress { + return IpAddress{ + IpAddress: &store.IpAddress{}, + } +} + +// Clone an IpAddress +func (c *IpAddress) Clone() *IpAddress { + cp := proto.Clone(c.IpAddress) + return &IpAddress{ + IpAddress: cp.(*store.IpAddress), + } +} + +// TableName returns the table name. +func (c *IpAddress) TableName() string { + if c.tableName != "" { + return c.tableName + } + return defaultIpAddressTableName +} + +// SetTableName sets the table name. +func (c *IpAddress) SetTableName(n string) { + c.tableName = n +} diff --git a/internal/host/options.go b/internal/host/options.go new file mode 100644 index 000000000..6e2afe4d6 --- /dev/null +++ b/internal/host/options.go @@ -0,0 +1,46 @@ +package host + +// GetOpts - iterate the inbound Options and return a struct +func GetOpts(opt ...Option) (options, error) { + opts := getDefaultOptions() + for _, o := range opt { + if err := o(&opts); err != nil { + return opts, err + } + } + return opts, nil +} + +// Option - how Options are passed as arguments. +type Option func(*options) error + +// options = how options are represented +type options struct { + WithLimit int + WithOrderByCreateTime bool + Ascending bool +} + +func getDefaultOptions() options { + return options{} +} + +// WithLimit provides an option to provide a limit. Intentionally allowing +// negative integers. If WithLimit < 0, then unlimited results are +// returned. If WithLimit == 0, then default limits are used for results. +func WithLimit(l int) Option { + return func(o *options) error { + o.WithLimit = l + return nil + } +} + +// WithOrderByCreateTime provides an option to specify ordering by the +// CreateTime field. +func WithOrderByCreateTime(ascending bool) Option { + return func(o *options) error { + o.WithOrderByCreateTime = true + o.Ascending = ascending + return nil + } +} diff --git a/internal/host/options_test.go b/internal/host/options_test.go new file mode 100644 index 000000000..b55d9148c --- /dev/null +++ b/internal/host/options_test.go @@ -0,0 +1,34 @@ +package host + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_GetOpts(t *testing.T) { + t.Parallel() + t.Run("WithLimit", func(t *testing.T) { + opts, err := GetOpts(WithLimit(1)) + require.NoError(t, err) + testOpts := getDefaultOptions() + testOpts.WithLimit = 1 + assert.Equal(t, opts, testOpts) + }) + t.Run("WithOrderByCreateTime-desc", func(t *testing.T) { + opts, err := GetOpts(WithOrderByCreateTime(false)) + require.NoError(t, err) + testOpts := getDefaultOptions() + testOpts.WithOrderByCreateTime = true + assert.Equal(t, opts, testOpts) + }) + t.Run("WithOrderByCreateTime-asc", func(t *testing.T) { + opts, err := GetOpts(WithOrderByCreateTime(true)) + require.NoError(t, err) + testOpts := getDefaultOptions() + testOpts.WithOrderByCreateTime = true + testOpts.Ascending = true + assert.Equal(t, opts, testOpts) + }) +} diff --git a/internal/host/plugin/client.go b/internal/host/plugin/client.go new file mode 100644 index 000000000..7c2d3271f --- /dev/null +++ b/internal/host/plugin/client.go @@ -0,0 +1,48 @@ +package plugin + +import ( + "context" + + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" + "google.golang.org/grpc" +) + +var _ plgpb.HostPluginServiceClient = (*WrappingPluginClient)(nil) + +// WrappingPluginClient provides a wrapper around a Server implementation that +// can be used when loading a plugin in-memory +type WrappingPluginClient struct { + Server plgpb.HostPluginServiceServer +} + +func NewWrappingPluginClient(s plgpb.HostPluginServiceServer) *WrappingPluginClient { + return &WrappingPluginClient{Server: s} +} + +func (tpc *WrappingPluginClient) OnCreateCatalog(ctx context.Context, req *plgpb.OnCreateCatalogRequest, opts ...grpc.CallOption) (*plgpb.OnCreateCatalogResponse, error) { + return tpc.Server.OnCreateCatalog(ctx, req) +} + +func (tpc *WrappingPluginClient) OnUpdateCatalog(ctx context.Context, req *plgpb.OnUpdateCatalogRequest, opts ...grpc.CallOption) (*plgpb.OnUpdateCatalogResponse, error) { + return tpc.Server.OnUpdateCatalog(ctx, req) +} + +func (tpc *WrappingPluginClient) OnDeleteCatalog(ctx context.Context, req *plgpb.OnDeleteCatalogRequest, opts ...grpc.CallOption) (*plgpb.OnDeleteCatalogResponse, error) { + return tpc.Server.OnDeleteCatalog(ctx, req) +} + +func (tpc *WrappingPluginClient) OnCreateSet(ctx context.Context, req *plgpb.OnCreateSetRequest, opts ...grpc.CallOption) (*plgpb.OnCreateSetResponse, error) { + return tpc.Server.OnCreateSet(ctx, req) +} + +func (tpc *WrappingPluginClient) OnUpdateSet(ctx context.Context, req *plgpb.OnUpdateSetRequest, opts ...grpc.CallOption) (*plgpb.OnUpdateSetResponse, error) { + return tpc.Server.OnUpdateSet(ctx, req) +} + +func (tpc *WrappingPluginClient) OnDeleteSet(ctx context.Context, req *plgpb.OnDeleteSetRequest, opts ...grpc.CallOption) (*plgpb.OnDeleteSetResponse, error) { + return tpc.Server.OnDeleteSet(ctx, req) +} + +func (tpc *WrappingPluginClient) ListHosts(ctx context.Context, req *plgpb.ListHostsRequest, opts ...grpc.CallOption) (*plgpb.ListHostsResponse, error) { + return tpc.Server.ListHosts(ctx, req) +} diff --git a/internal/host/plugin/host.go b/internal/host/plugin/host.go new file mode 100644 index 000000000..b83e9dd85 --- /dev/null +++ b/internal/host/plugin/host.go @@ -0,0 +1,161 @@ +package plugin + +import ( + "context" + "sort" + "strings" + + "github.com/hashicorp/boundary/internal/db/timestamp" + "github.com/hashicorp/boundary/internal/host/plugin/store" + "github.com/hashicorp/boundary/internal/oplog" + "google.golang.org/protobuf/proto" +) + +// A Host is a temporarily cached plugin based resource. The source of truth +// of the data contained here is provided by the service backing the plugin for +// this host. The plugin for this host can be found by looking at the plugin +// field for this host's host catalog. +type Host struct { + *store.Host + PluginId string `gorm:"-"` + SetIds []string `gorm:"-"` + tableName string `gorm:"-"` +} + +// NewHost creates a new in memory Host assigned to catalogId with an address. +// Supported options: WithName, WithDescription, WithIpAddresses, WithDnsNames, +// WithPluginId, WithPublicId. Others ignored. +func NewHost(ctx context.Context, catalogId, externalId string, opt ...Option) *Host { + opts := getOpts(opt...) + + h := &Host{ + PluginId: opts.withPluginId, + Host: &store.Host{ + PublicId: opts.withPublicId, + CatalogId: catalogId, + ExternalId: externalId, + Name: opts.withName, + Description: opts.withDescription, + }, + } + if len(opts.withIpAddresses) > 0 { + h.IpAddresses = make([]string, 0, len(opts.withIpAddresses)) + h.IpAddresses = append(h.IpAddresses, opts.withIpAddresses...) + } + if len(opts.withDnsNames) > 0 { + h.DnsNames = make([]string, 0, len(opts.withDnsNames)) + h.DnsNames = append(h.DnsNames, opts.withDnsNames...) + } + + return h +} + +// For compatibility with the general Host type +func (h *Host) GetAddress() string { + return "" +} + +// TableName returns the table name for the host set. +func (s *Host) TableName() string { + if s.tableName != "" { + return s.tableName + } + return "host_plugin_host" +} + +// SetTableName sets the table name. If the caller attempts to +// set the name to "" the name will be reset to the default name. +func (s *Host) SetTableName(n string) { + s.tableName = n +} + +func allocHost() *Host { + return &Host{ + Host: &store.Host{}, + } +} + +func (h *Host) clone() *Host { + cp := proto.Clone(h.Host) + nh := &Host{ + PluginId: h.PluginId, + Host: cp.(*store.Host), + } + switch { + case h.SetIds == nil: + case len(h.SetIds) == 0: + nh.SetIds = make([]string, 0) + default: + nh.SetIds = make([]string, len(h.SetIds)) + copy(nh.SetIds, h.SetIds) + } + return nh +} + +func (h *Host) oplog(op oplog.OpType) oplog.Metadata { + metadata := oplog.Metadata{ + "resource-public-id": []string{h.PublicId}, + "resource-type": []string{"plugin-host"}, + "op-type": []string{op.String()}, + } + if h.CatalogId != "" { + metadata["catalog-id"] = []string{h.CatalogId} + } + return metadata +} + +// hostAgg is a view that aggregates the host's value objects in to +// string fields delimited with the aggregateDelimiter of "|" +type hostAgg struct { + PublicId string `gorm:"primary_key"` + CatalogId string + ScopeId string + ExternalId string + PluginId string + Name string + Description string + CreateTime *timestamp.Timestamp + UpdateTime *timestamp.Timestamp + IpAddresses string + DnsNames string + SetIds string +} + +func (agg *hostAgg) toHost() *Host { + const aggregateDelimiter = "|" + h := allocHost() + h.PublicId = agg.PublicId + h.CatalogId = agg.CatalogId + h.ExternalId = agg.ExternalId + h.PluginId = agg.PluginId + h.Name = agg.Name + h.Description = agg.Description + h.CreateTime = agg.CreateTime + h.UpdateTime = agg.UpdateTime + + if agg.IpAddresses != "" { + h.IpAddresses = strings.Split(agg.IpAddresses, aggregateDelimiter) + sort.Strings(h.IpAddresses) + } + + if agg.DnsNames != "" { + h.DnsNames = strings.Split(agg.DnsNames, aggregateDelimiter) + sort.Strings(h.DnsNames) + } + + if agg.SetIds != "" { + h.SetIds = strings.Split(agg.SetIds, aggregateDelimiter) + sort.Strings(h.SetIds) + } + + return h +} + +// TableName returns the table name for gorm +func (agg *hostAgg) TableName() string { + return "host_plugin_host_with_value_obj_and_set_memberships" +} + +func (agg *hostAgg) GetPublicId() string { + return agg.PublicId +} diff --git a/internal/host/plugin/host_address_test.go b/internal/host/plugin/host_address_test.go new file mode 100644 index 000000000..41704b2d3 --- /dev/null +++ b/internal/host/plugin/host_address_test.go @@ -0,0 +1,418 @@ +package plugin + +import ( + "context" + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/host" + "github.com/hashicorp/boundary/internal/host/store" + "github.com/hashicorp/boundary/internal/iam" + hostplugin "github.com/hashicorp/boundary/internal/plugin/host" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestHostDnsName_Create(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + w := db.New(conn) + wrapper := db.TestWrapper(t) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + plg := hostplugin.TestPlugin(t, conn, "test") + cat := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + host1 := TestHost(t, conn, cat.GetPublicId(), "external") + + type args struct { + hostId string + name string + } + + tests := []struct { + name string + args args + want *host.DnsName + wantNewErr bool + skipNewFunc bool + wantDbErr bool + }{ + { + name: "blank-host-id-validate", + args: args{ + hostId: "", + name: "foo.bar.com", + }, + wantNewErr: true, + }, + { + name: "blank-name-validate", + args: args{ + hostId: host1.GetPublicId(), + name: "", + }, + wantNewErr: true, + }, + { + name: "blank-host-id-db", + args: args{ + name: "foo.bar.com", + }, + skipNewFunc: true, + wantDbErr: true, + }, + { + name: "blank-name-db", + args: args{ + hostId: host1.GetPublicId(), + }, + skipNewFunc: true, + wantDbErr: true, + }, + { + name: "valid", + args: args{ + hostId: host1.GetPublicId(), + name: "foo.bar.com", + }, + want: &host.DnsName{ + DnsName: &store.DnsName{ + HostId: host1.GetPublicId(), + Name: "foo.bar.com", + }, + }, + }, + { + name: "duplicate-name", + args: args{ + hostId: host1.GetPublicId(), + name: "foo.bar.com", + }, + want: &host.DnsName{ + DnsName: &store.DnsName{ + HostId: host1.GetPublicId(), + Name: "foo.bar.com", + }, + }, + wantDbErr: true, + }, + { + name: "valid-second", + args: args{ + hostId: host1.GetPublicId(), + name: "baz.bar.com", + }, + want: &host.DnsName{ + DnsName: &store.DnsName{ + HostId: host1.GetPublicId(), + Name: "baz.bar.com", + }, + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + var got *host.DnsName + var err error + if !tt.skipNewFunc { + got, err = host.NewDnsName(ctx, tt.args.hostId, tt.args.name) + if tt.wantNewErr { + require.Error(err) + return + } + require.NoError(err) + require.Equal(tt.want, got) + } else { + got = &host.DnsName{ + DnsName: &store.DnsName{ + HostId: tt.args.hostId, + Name: tt.args.name, + }, + } + } + + require.NotNil(got) + err = w.Create(ctx, got) + if tt.wantDbErr { + require.Error(err) + return + } + require.NoError(err) + }) + } +} + +func TestHostIpAddress_Create(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + w := db.New(conn) + wrapper := db.TestWrapper(t) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + plg := hostplugin.TestPlugin(t, conn, "test") + cat := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + host1 := TestHost(t, conn, cat.GetPublicId(), "external") + + type args struct { + hostId string + address string + } + + tests := []struct { + name string + args args + want *host.IpAddress + wantNewErr bool + skipNewFunc bool + wantDbErr bool + }{ + { + name: "blank-host-id-validate", + args: args{ + hostId: "", + address: "1.2.3.4", + }, + wantNewErr: true, + }, + { + name: "blank-name-validate", + args: args{ + hostId: host1.GetPublicId(), + address: "", + }, + wantNewErr: true, + }, + { + name: "bad-address-validate", + args: args{ + hostId: host1.GetPublicId(), + address: "foo.bar.com", + }, + wantNewErr: true, + }, + { + name: "blank-host-id-db", + args: args{ + address: "1.2.3.4", + }, + skipNewFunc: true, + wantDbErr: true, + }, + { + name: "blank-address-db", + args: args{ + hostId: host1.GetPublicId(), + }, + skipNewFunc: true, + wantDbErr: true, + }, + { + name: "valid", + args: args{ + hostId: host1.GetPublicId(), + address: "1.2.3.4", + }, + want: &host.IpAddress{ + IpAddress: &store.IpAddress{ + HostId: host1.GetPublicId(), + Address: "1.2.3.4", + }, + }, + }, + { + name: "duplicate-name", + args: args{ + hostId: host1.GetPublicId(), + address: "1.2.3.4", + }, + want: &host.IpAddress{ + IpAddress: &store.IpAddress{ + HostId: host1.GetPublicId(), + Address: "1.2.3.4", + }, + }, + wantDbErr: true, + }, + { + name: "valid-second", + args: args{ + hostId: host1.GetPublicId(), + address: "2.3.4.5", + }, + want: &host.IpAddress{ + IpAddress: &store.IpAddress{ + HostId: host1.GetPublicId(), + Address: "2.3.4.5", + }, + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + var got *host.IpAddress + var err error + if !tt.skipNewFunc { + got, err = host.NewIpAddress(ctx, tt.args.hostId, tt.args.address) + if tt.wantNewErr { + require.Error(err) + return + } + require.NoError(err) + require.Equal(tt.want, got) + } else { + got = &host.IpAddress{ + IpAddress: &store.IpAddress{ + HostId: tt.args.hostId, + Address: tt.args.address, + }, + } + } + + require.NotNil(got) + err = w.Create(ctx, got) + if tt.wantDbErr { + require.Error(err) + return + } + require.NoError(err) + }) + } +} + +func TestHostDnsName_Delete(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + w := db.New(conn) + wrapper := db.TestWrapper(t) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + plg := hostplugin.TestPlugin(t, conn, "test") + cat := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + host1 := TestHost(t, conn, cat.GetPublicId(), "external") + addr1, err := host.NewDnsName(ctx, host1.GetPublicId(), "addr1.foo.com") + require.NoError(t, err) + require.NoError(t, w.Create(ctx, addr1)) + + type args struct { + hostId string + name string + } + + tests := []struct { + name string + args args + wantDelete bool + wantError bool + }{ + { + name: "wrong_host_id", + args: args{ + hostId: "something", + name: addr1.GetName(), + }, + }, + { + name: "valid", + args: args{ + hostId: addr1.GetHostId(), + name: addr1.GetName(), + }, + wantDelete: true, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + got := &host.DnsName{ + DnsName: &store.DnsName{ + HostId: tt.args.hostId, + Name: tt.args.name, + }, + } + require.NoError(t, err) + require.NotNil(t, got) + k, err := w.Delete(ctx, got) + if tt.wantError { + assert.Error(t, err) + return + } + require.NoError(t, err) + if tt.wantDelete { + assert.Equal(t, 1, k) + } else { + assert.Equal(t, 0, k) + } + }) + } +} + +func TestHostIpAddress_Delete(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + w := db.New(conn) + wrapper := db.TestWrapper(t) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + plg := hostplugin.TestPlugin(t, conn, "test") + cat := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + host1 := TestHost(t, conn, cat.GetPublicId(), "external") + addr1, err := host.NewIpAddress(ctx, host1.GetPublicId(), "1.2.3.4") + require.NoError(t, err) + require.NoError(t, w.Create(ctx, addr1)) + + type args struct { + hostId string + address string + } + + tests := []struct { + name string + args args + wantDelete bool + wantError bool + }{ + { + name: "wrong_host_id", + args: args{ + hostId: "something", + address: addr1.GetAddress(), + }, + }, + { + name: "valid", + args: args{ + hostId: addr1.GetHostId(), + address: addr1.GetAddress(), + }, + wantDelete: true, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + got := &host.IpAddress{ + IpAddress: &store.IpAddress{ + HostId: tt.args.hostId, + Address: tt.args.address, + }, + } + require.NoError(t, err) + require.NotNil(t, got) + k, err := w.Delete(ctx, got) + if tt.wantError { + assert.Error(t, err) + return + } + require.NoError(t, err) + if tt.wantDelete { + assert.Equal(t, 1, k) + } else { + assert.Equal(t, 0, k) + } + }) + } +} diff --git a/internal/host/plugin/host_catalog.go b/internal/host/plugin/host_catalog.go new file mode 100644 index 000000000..8a29da5f8 --- /dev/null +++ b/internal/host/plugin/host_catalog.go @@ -0,0 +1,151 @@ +// Package plugin provides a plugin host catalog, and plugin host set resource +// which are used to interact with a host plugin as well as a repository to +// perform CRUDL and custom actions on these resource types. +package plugin + +import ( + "context" + + "github.com/hashicorp/boundary/internal/db/timestamp" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/host/plugin/store" + "github.com/hashicorp/boundary/internal/oplog" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/structpb" +) + +// A HostCatalog contains plugin host sets. It is owned by +// a scope. +type HostCatalog struct { + *store.HostCatalog + tableName string `gorm:"-"` + + Secrets *structpb.Struct `gorm:"-"` +} + +// NewHostCatalog creates a new in memory HostCatalog assigned to a scopeId +// and pluginId. Name and description are the only valid options. All other +// options are ignored. +func NewHostCatalog(ctx context.Context, scopeId, pluginId string, opt ...Option) (*HostCatalog, error) { + const op = "plugin.NewHostCatalog" + opts := getOpts(opt...) + + attrs, err := proto.Marshal(opts.withAttributes) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithCode(errors.InvalidParameter)) + } + + hc := &HostCatalog{ + HostCatalog: &store.HostCatalog{ + ScopeId: scopeId, + PluginId: pluginId, + Name: opts.withName, + Description: opts.withDescription, + Attributes: attrs, + }, + Secrets: opts.withSecrets, + } + return hc, nil +} + +func allocHostCatalog() *HostCatalog { + return &HostCatalog{ + HostCatalog: &store.HostCatalog{}, + } +} + +// clone provides a deep copy of the HostCatalog with the exception of the +// secret. The secret shallow copied. +func (c *HostCatalog) clone() *HostCatalog { + cp := proto.Clone(c.HostCatalog) + newSecret := proto.Clone(c.Secrets) + + hc := &HostCatalog{ + HostCatalog: cp.(*store.HostCatalog), + Secrets: newSecret.(*structpb.Struct), + } + // proto.Clone will convert slices with length and capacity of 0 to nil. + // Fix this since gorm treats empty slices differently than nil. + if c.Attributes != nil && len(c.Attributes) == 0 && hc.Attributes == nil { + hc.Attributes = []byte{} + } + return hc +} + +// TableName returns the table name for the host catalog. +func (c *HostCatalog) TableName() string { + if c.tableName != "" { + return c.tableName + } + return "host_plugin_catalog" +} + +// SetTableName sets the table name. If the caller attempts to +// set the name to "" the name will be reset to the default name. +func (c *HostCatalog) SetTableName(n string) { + c.tableName = n +} + +func (s *HostCatalog) oplog(op oplog.OpType) oplog.Metadata { + metadata := oplog.Metadata{ + "resource-public-id": []string{s.PublicId}, + "resource-type": []string{"plugin-host-catalog"}, + "op-type": []string{op.String()}, + } + if s.ScopeId != "" { + metadata["scope-id"] = []string{s.ScopeId} + } + return metadata +} + +type catalogAgg struct { + PublicId string `gorm:"primary_key"` + ScopeId string + PluginId string + Name string + Description string + CreateTime *timestamp.Timestamp + UpdateTime *timestamp.Timestamp + Version uint32 + Attributes []byte + Secret []byte + KeyId string + PersistedCreateTime *timestamp.Timestamp + PersistedUpdateTime *timestamp.Timestamp +} + +func (agg *catalogAgg) toCatalogAndPersisted() (*HostCatalog, *HostCatalogSecret) { + if agg == nil { + return nil, nil + } + c := allocHostCatalog() + c.PublicId = agg.PublicId + c.ScopeId = agg.ScopeId + c.PluginId = agg.PluginId + c.Name = agg.Name + c.Description = agg.Description + c.CreateTime = agg.CreateTime + c.UpdateTime = agg.UpdateTime + c.Version = agg.Version + c.Attributes = agg.Attributes + + var s *HostCatalogSecret + if len(agg.Secret) > 0 { + s = allocHostCatalogSecret() + s.CatalogId = agg.PublicId + s.CtSecret = agg.Secret + s.KeyId = agg.KeyId + s.CreateTime = agg.PersistedCreateTime + s.UpdateTime = agg.PersistedUpdateTime + } + return c, s +} + +// TableName returns the table name for gorm +func (agg *catalogAgg) TableName() string { + return "host_plugin_catalog_with_secret" +} + +func (agg *catalogAgg) GetPublicId() string { + return agg.PublicId +} diff --git a/internal/host/plugin/host_catalog_secret.go b/internal/host/plugin/host_catalog_secret.go new file mode 100644 index 000000000..84eda4867 --- /dev/null +++ b/internal/host/plugin/host_catalog_secret.go @@ -0,0 +1,130 @@ +package plugin + +import ( + "context" + "database/sql" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/host/plugin/store" + "github.com/hashicorp/boundary/internal/oplog" + wrapping "github.com/hashicorp/go-kms-wrapping" + "github.com/hashicorp/go-kms-wrapping/structwrapping" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/structpb" +) + +// HostCatalogSecret contains the encrypted secret for a host catalog. +// It is owned by a HostCatalog. +type HostCatalogSecret struct { + *store.HostCatalogSecret + tableName string `gorm:"-"` +} + +// newHostCatalogSecret creates an in memory host catalog secret. +// All options are ignored. +func newHostCatalogSecret(ctx context.Context, catalogId string, secret *structpb.Struct, _ ...Option) (*HostCatalogSecret, error) { + const op = "plugin.newHostCatlogSecret" + hcs := &HostCatalogSecret{ + HostCatalogSecret: &store.HostCatalogSecret{ + CatalogId: catalogId, + }, + } + + if secret != nil { + attrs, err := proto.Marshal(secret) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithCode(errors.InvalidParameter)) + } + hcs.Secret = attrs + } + return hcs, nil +} + +func allocHostCatalogSecret() *HostCatalogSecret { + return &HostCatalogSecret{ + HostCatalogSecret: &store.HostCatalogSecret{}, + } +} + +func (c *HostCatalogSecret) clone() *HostCatalogSecret { + cp := proto.Clone(c.HostCatalogSecret) + return &HostCatalogSecret{ + HostCatalogSecret: cp.(*store.HostCatalogSecret), + } +} + +// TableName returns the table name for the host catalog. +func (c *HostCatalogSecret) TableName() string { + if c.tableName != "" { + return c.tableName + } + return "host_plugin_catalog_secret" +} + +// SetTableName sets the table name. If the caller attempts to +// set the name to "" the name will be reset to the default name. +func (c *HostCatalogSecret) SetTableName(n string) { + c.tableName = n +} + +func (c *HostCatalogSecret) encrypt(ctx context.Context, cipher wrapping.Wrapper) error { + const op = "plugin.(HostCatalogSecret).encrypt" + if len(c.Secret) == 0 { + return errors.New(ctx, errors.InvalidParameter, op, "no attributes defined") + } + if err := structwrapping.WrapStruct(ctx, cipher, c.HostCatalogSecret, nil); err != nil { + return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt)) + } + c.KeyId = cipher.KeyID() + c.Secret = nil + return nil +} + +func (c *HostCatalogSecret) decrypt(ctx context.Context, cipher wrapping.Wrapper) error { + const op = "plugin.(HostCatalogSecret).decrypt" + if err := structwrapping.UnwrapStruct(ctx, cipher, c.HostCatalogSecret, nil); err != nil { + return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt)) + } + c.CtSecret = nil + return nil +} + +// TODO(ICU-2835): this is necessary as update logic requires a static +// public_id or private_id field. Once post-lookup updates can +// support alternate primary key fields, we can port this to native +// Update and upserts w/OnConflict. +func (c *HostCatalogSecret) upsertQuery() (query string, queryValues []interface{}) { + query = upsertHostCatalogSecretQuery + queryValues = []interface{}{ + sql.Named("catalog_id", c.CatalogId), + sql.Named("secret", c.CtSecret), + sql.Named("key_id", c.KeyId), + } + return +} + +func (c *HostCatalogSecret) deleteQuery() (query string, queryValues []interface{}) { + query = deleteHostCatalogSecretQuery + queryValues = []interface{}{ + sql.Named("catalog_id", c.CatalogId), + } + return +} + +func (c *HostCatalogSecret) oplogMessage(opType db.OpType) *oplog.Message { + msg := oplog.Message{ + Message: c.clone(), + TypeName: c.TableName(), + } + switch opType { + case db.CreateOp: + msg.OpType = oplog.OpType_OP_TYPE_CREATE + case db.UpdateOp: + msg.OpType = oplog.OpType_OP_TYPE_UPDATE + msg.FieldMaskPaths = []string{"secret", "key_id"} + case db.DeleteOp: + msg.OpType = oplog.OpType_OP_TYPE_DELETE + } + return &msg +} diff --git a/internal/host/plugin/host_catalog_secret_test.go b/internal/host/plugin/host_catalog_secret_test.go new file mode 100644 index 000000000..09bb7af02 --- /dev/null +++ b/internal/host/plugin/host_catalog_secret_test.go @@ -0,0 +1,174 @@ +package plugin + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/host/plugin/store" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/plugin/host" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/structpb" +) + +func TestHostCatalogSecret_New(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kkms := kms.TestKms(t, conn, wrapper) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + plg := host.TestPlugin(t, conn, "test") + cat := TestCatalog(t, conn, prj.GetPublicId(), plg.GetPublicId()) + + type args struct { + catalogId string + attrs *structpb.Struct + } + + tests := []struct { + name string + args args + want *HostCatalogSecret + wantErr bool + wantEncryptErr bool + }{ + { + name: "blank-catalog-id", + args: args{ + catalogId: "", + }, + want: &HostCatalogSecret{ + HostCatalogSecret: &store.HostCatalogSecret{}, + }, + wantEncryptErr: true, + }, + { + name: "no-attributes", + args: args{ + catalogId: cat.GetPublicId(), + }, + want: &HostCatalogSecret{ + HostCatalogSecret: &store.HostCatalogSecret{ + CatalogId: cat.GetPublicId(), + }, + }, + wantEncryptErr: true, + }, + { + name: "valid", + args: args{ + catalogId: cat.GetPublicId(), + attrs: func() *structpb.Struct { + st, err := structpb.NewStruct(map[string]interface{}{"foo": "bar"}) + require.NoError(t, err) + return st + }(), + }, + want: &HostCatalogSecret{ + HostCatalogSecret: &store.HostCatalogSecret{ + CatalogId: cat.GetPublicId(), + Secret: func() []byte { + st, err := structpb.NewStruct(map[string]interface{}{"foo": "bar"}) + require.NoError(t, err) + b, err := proto.Marshal(st) + require.NoError(t, err) + return b + }(), + }, + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + + databaseWrapper, err := kkms.GetWrapper(ctx, prj.PublicId, kms.KeyPurposeDatabase) + require.NoError(t, err) + require.NotNil(t, databaseWrapper) + + got, err := newHostCatalogSecret(context.Background(), tt.args.catalogId, tt.args.attrs) + if tt.wantErr { + assert.Error(t, err) + require.Nil(t, got) + return + } + require.NoError(t, err) + require.NotNil(t, got) + + assert.Empty(t, got.CtSecret) + assert.Equal(t, tt.want, got) + + err = got.encrypt(ctx, databaseWrapper) + if tt.wantEncryptErr { + require.Error(t, err) + return + } + require.NoError(t, err) + require.NoError(t, got.decrypt(ctx, databaseWrapper)) + }) + } +} + +func TestHostCatalogSecret_Custom_Queries(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kkms := kms.TestKms(t, conn, wrapper) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + plg := host.TestPlugin(t, conn, "test") + cat := TestCatalog(t, conn, prj.GetPublicId(), plg.GetPublicId()) + databaseWrapper, err := kkms.GetWrapper(ctx, prj.PublicId, kms.KeyPurposeDatabase) + require.NoError(t, err) + + hcs, err := newHostCatalogSecret(ctx, cat.GetPublicId(), + &structpb.Struct{Fields: map[string]*structpb.Value{"foo": structpb.NewStringValue("bar")}}) + require.NoError(t, err) + assert.NoError(t, hcs.encrypt(ctx, databaseWrapper)) + q, v := hcs.upsertQuery() + _, err = rw.Exec(ctx, q, v) + assert.NoError(t, err) + + found, err := newHostCatalogSecret(ctx, cat.GetPublicId(), nil) + require.NoError(t, err) + assert.NoError(t, rw.LookupWhere(ctx, found, "catalog_id=?", found.GetCatalogId())) + require.NoError(t, found.decrypt(ctx, databaseWrapper)) + require.NoError(t, hcs.decrypt(ctx, databaseWrapper)) + // update the created/updated time from the original + hcs.CreateTime, hcs.UpdateTime = found.CreateTime, found.UpdateTime + assert.Empty(t, cmp.Diff(hcs, found, protocmp.Transform())) + + // Update the secret and see the value updated. + updated, err := newHostCatalogSecret(ctx, cat.GetPublicId(), + &structpb.Struct{Fields: map[string]*structpb.Value{"updated": structpb.NewStringValue("value")}}) + require.NoError(t, err) + assert.NoError(t, updated.encrypt(ctx, databaseWrapper)) + q, v = updated.upsertQuery() + _, err = rw.Exec(ctx, q, v) + assert.NoError(t, err) + + assert.NoError(t, rw.LookupWhere(ctx, found, "catalog_id=?", found.GetCatalogId())) + require.NoError(t, found.decrypt(ctx, databaseWrapper)) + require.NoError(t, updated.decrypt(ctx, databaseWrapper)) + // set the created time to the first and update time from the newly found + updated.CreateTime, updated.UpdateTime = hcs.CreateTime, found.UpdateTime + assert.Empty(t, cmp.Diff(updated, found, protocmp.Transform())) + + // Try to delete this secret. + q, v = updated.deleteQuery() + _, err = rw.Exec(ctx, q, v) + assert.NoError(t, err) + err = rw.LookupWhere(ctx, found, "catalog_id=?", found.GetCatalogId()) + assert.Error(t, err) + assert.True(t, errors.IsNotFoundError(err)) +} diff --git a/internal/host/plugin/host_catalog_test.go b/internal/host/plugin/host_catalog_test.go new file mode 100644 index 000000000..cd7d90578 --- /dev/null +++ b/internal/host/plugin/host_catalog_test.go @@ -0,0 +1,328 @@ +package plugin + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/host/plugin/store" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/plugin/host" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/structpb" +) + +func TestHostCatalog_Create(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + plg := host.TestPlugin(t, conn, "test") + + type args struct { + pluginId string + scopeId string + opts []Option + } + + tests := []struct { + name string + args args + want *HostCatalog + wantErr bool + wantCreateErr bool + }{ + { + name: "blank-scopeId", + args: args{ + pluginId: plg.GetPublicId(), + scopeId: "", + }, + want: &HostCatalog{ + HostCatalog: &store.HostCatalog{ + PluginId: plg.GetPublicId(), + Attributes: []byte{}, + }, + }, + wantCreateErr: true, + }, + { + name: "blank-pluginId", + args: args{ + pluginId: "", + scopeId: prj.GetPublicId(), + }, + want: &HostCatalog{ + HostCatalog: &store.HostCatalog{ + ScopeId: prj.GetPublicId(), + Attributes: []byte{}, + }, + }, + wantCreateErr: true, + }, + { + name: "valid-no-options", + args: args{ + pluginId: plg.GetPublicId(), + scopeId: prj.GetPublicId(), + }, + want: &HostCatalog{ + HostCatalog: &store.HostCatalog{ + PluginId: plg.GetPublicId(), + ScopeId: prj.GetPublicId(), + Attributes: []byte{}, + }, + }, + }, + { + name: "valid-with-name", + args: args{ + pluginId: plg.GetPublicId(), + scopeId: prj.GetPublicId(), + opts: []Option{ + WithName("test-name"), + }, + }, + want: &HostCatalog{ + HostCatalog: &store.HostCatalog{ + PluginId: plg.GetPublicId(), + ScopeId: prj.GetPublicId(), + Name: "test-name", + Attributes: []byte{}, + }, + }, + }, + { + name: "valid-with-description", + args: args{ + pluginId: plg.GetPublicId(), + scopeId: prj.GetPublicId(), + opts: []Option{ + WithDescription("test-description"), + }, + }, + want: &HostCatalog{ + HostCatalog: &store.HostCatalog{ + PluginId: plg.GetPublicId(), + ScopeId: prj.GetPublicId(), + Description: "test-description", + Attributes: []byte{}, + }, + }, + }, + { + name: "valid-with-attributes", + args: args{ + pluginId: plg.GetPublicId(), + scopeId: prj.GetPublicId(), + opts: []Option{ + WithAttributes(&structpb.Struct{Fields: map[string]*structpb.Value{"foo": structpb.NewStringValue("bar")}}), + }, + }, + want: func() *HostCatalog { + hc := &HostCatalog{ + HostCatalog: &store.HostCatalog{ + PluginId: plg.GetPublicId(), + ScopeId: prj.GetPublicId(), + }, + } + var err error + hc.Attributes, err = proto.Marshal(&structpb.Struct{Fields: map[string]*structpb.Value{"foo": structpb.NewStringValue("bar")}}) + require.NoError(t, err) + return hc + }(), + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + got, err := NewHostCatalog(ctx, tt.args.scopeId, tt.args.pluginId, tt.args.opts...) + require.NoError(t, err) + require.NotNil(t, got) + + assert.Emptyf(t, got.PublicId, "PublicId set") + assert.Equal(t, tt.want, got) + + id, err := newHostCatalogId(ctx) + assert.NoError(t, err) + + tt.want.PublicId = id + got.PublicId = id + + w := db.New(conn) + err = w.Create(ctx, got) + if tt.wantCreateErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + found := &HostCatalog{ + HostCatalog: &store.HostCatalog{ + PublicId: id, + }, + } + require.NoError(t, w.LookupById(ctx, found)) + assert.Empty(t, cmp.Diff(got.HostCatalog, found.HostCatalog, protocmp.Transform()), "%q compared to %q", got.Attributes, found.Attributes) + } + }) + } +} + +func TestHostCatalog_Create_DuplicateNames(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + w := db.New(conn) + wrapper := db.TestWrapper(t) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + _, prj2 := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + plg := host.TestPlugin(t, conn, "test1") + plg2 := host.TestPlugin(t, conn, "test2") + + got, err := NewHostCatalog(ctx, prj.GetPublicId(), plg.GetPublicId(), WithName("duplicate")) + require.NoError(t, err) + got.PublicId, err = newHostCatalogId(ctx) + require.NoError(t, err) + require.NoError(t, w.Create(ctx, got)) + + // Can't create another resource with the same name in the same scope + got.PublicId, err = newHostCatalogId(ctx) + require.NoError(t, err) + assert.Error(t, w.Create(ctx, got)) + + // Can't create another resource with same name in same scope even for different plugin + got, err = NewHostCatalog(ctx, prj.GetPublicId(), plg2.GetPublicId(), WithName("duplicate")) + require.NoError(t, err) + got.PublicId, err = newHostCatalogId(ctx) + require.NoError(t, err) + assert.Error(t, w.Create(ctx, got)) + + // Can create another resource with same name in different scope even for same plugin + got, err = NewHostCatalog(ctx, prj2.GetPublicId(), plg.GetPublicId(), WithName("duplicate")) + require.NoError(t, err) + got.PublicId, err = newHostCatalogId(ctx) + require.NoError(t, err) + assert.NoError(t, w.Create(ctx, got)) +} + +func TestHostCatalog_Delete(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + plg := host.TestPlugin(t, conn, "test") + cat := TestCatalog(t, conn, prj.GetPublicId(), plg.GetPublicId()) + ignoredCat := TestCatalog(t, conn, prj.GetPublicId(), plg.GetPublicId()) + _ = ignoredCat + tests := []struct { + name string + cat *HostCatalog + rowsDeleted int + }{ + { + name: "valid", + cat: &HostCatalog{ + HostCatalog: &store.HostCatalog{ + PublicId: cat.GetPublicId(), + }, + }, + rowsDeleted: 1, + }, + { + name: "bad-id", + cat: &HostCatalog{ + HostCatalog: &store.HostCatalog{ + PublicId: "bad-id", + }, + }, + rowsDeleted: 0, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + w := db.New(conn) + deletedRows, err := w.Delete(ctx, tt.cat) + require.NoError(t, err) + assert.Equal(t, tt.rowsDeleted, deletedRows) + + err = w.LookupById(ctx, tt.cat) + require.Error(t, err) + assert.True(t, errors.IsNotFoundError(err)) + }) + } +} + +func TestHostCatalog_Delete_Cascading(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + w := db.New(conn) + wrapper := db.TestWrapper(t) + ctx := context.Background() + + t.Run("delete-scope", func(t *testing.T) { + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + plg := host.TestPlugin(t, conn, "deletescope") + cat := TestCatalog(t, conn, prj.GetPublicId(), plg.GetPublicId()) + + deleted, err := w.Delete(ctx, prj) + require.NoError(t, err) + require.Equal(t, 1, deleted) + + err = w.LookupById(ctx, cat) + require.Error(t, err) + assert.True(t, errors.IsNotFoundError(err)) + }) + + t.Run("delete-plugin", func(t *testing.T) { + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + plg := host.TestPlugin(t, conn, "deleteplugin") + cat := TestCatalog(t, conn, prj.GetPublicId(), plg.GetPublicId()) + + deleted, err := w.Delete(ctx, plg) + require.NoError(t, err) + require.Equal(t, 1, deleted) + + err = w.LookupById(ctx, cat) + require.Error(t, err) + assert.True(t, errors.IsNotFoundError(err)) + }) +} + +func TestHostCatalog_SetTableName(t *testing.T) { + defaultTableName := "host_plugin_catalog" + tests := []struct { + name string + initialName string + setNameTo string + want string + }{ + { + name: "new-name", + initialName: "", + setNameTo: "new-name", + want: "new-name", + }, + { + name: "reset to default", + initialName: "initial", + setNameTo: "", + want: defaultTableName, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + def, err := NewHostCatalog(context.Background(), "", "") + require.NoError(t, err) + require.Equal(t, defaultTableName, def.TableName()) + s := &HostCatalog{ + HostCatalog: &store.HostCatalog{}, + tableName: tt.initialName, + } + s.SetTableName(tt.setNameTo) + assert.Equal(t, tt.want, s.TableName()) + }) + } +} diff --git a/internal/host/plugin/host_set.go b/internal/host/plugin/host_set.go new file mode 100644 index 000000000..193d9b845 --- /dev/null +++ b/internal/host/plugin/host_set.go @@ -0,0 +1,184 @@ +package plugin + +import ( + "context" + "fmt" + "sort" + "strconv" + "strings" + + "github.com/hashicorp/boundary/internal/db/timestamp" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/host/plugin/store" + "github.com/hashicorp/boundary/internal/oplog" + "google.golang.org/protobuf/proto" +) + +// A HostSet is a collection of hosts from the set's catalog. +type HostSet struct { + *store.HostSet + PluginId string `gorm:"-"` + HostIds []string `gorm:"-"` + PreferredEndpoints []string `gorm:"-"` + tableName string `gorm:"-"` +} + +// NewHostSet creates a new in memory HostSet assigned to catalogId. Attributes, +// name, description, and preferred endpoints are the only valid options. All +// other options are ignored. +func NewHostSet(ctx context.Context, catalogId string, opt ...Option) (*HostSet, error) { + const op = "plugin.NewHostSet" + opts := getOpts(opt...) + attrs, err := proto.Marshal(opts.withAttributes) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithCode(errors.InvalidParameter)) + } + + set := &HostSet{ + HostSet: &store.HostSet{ + CatalogId: catalogId, + Name: opts.withName, + Description: opts.withDescription, + SyncIntervalSeconds: opts.withSyncIntervalSeconds, + Attributes: attrs, + }, + PreferredEndpoints: opts.withPreferredEndpoints, + } + + return set, nil +} + +// TableName returns the table name for the host set. +func (s *HostSet) TableName() string { + if s.tableName != "" { + return s.tableName + } + return "host_plugin_set" +} + +// SetTableName sets the table name. If the caller attempts to +// set the name to "" the name will be reset to the default name. +func (s *HostSet) SetTableName(n string) { + s.tableName = n +} + +func allocHostSet() *HostSet { + return &HostSet{ + HostSet: &store.HostSet{}, + } +} + +func (s *HostSet) clone() *HostSet { + cp := proto.Clone(s.HostSet) + hs := &HostSet{ + HostSet: cp.(*store.HostSet), + PreferredEndpoints: s.PreferredEndpoints, + } + if s.Attributes != nil && len(s.Attributes) == 0 && hs.Attributes == nil { + hs.Attributes = []byte{} + } + return hs +} + +func (s *HostSet) oplog(op oplog.OpType) oplog.Metadata { + metadata := oplog.Metadata{ + "resource-public-id": []string{s.PublicId}, + "resource-type": []string{"plugin-host-set"}, + "op-type": []string{op.String()}, + } + if s.CatalogId != "" { + metadata["catalog-id"] = []string{s.CatalogId} + } + return metadata +} + +// hostSetAgg is a view that aggregates the host set's value objects in to +// string fields delimited with the aggregateDelimiter of "|" +type hostSetAgg struct { + PublicId string `gorm:"primary_key"` + CatalogId string + PluginId string + Name string + Description string + CreateTime *timestamp.Timestamp + UpdateTime *timestamp.Timestamp + LastSyncTime *timestamp.Timestamp + NeedSync bool + SyncIntervalSeconds int32 + Version uint32 + Attributes []byte + PreferredEndpoints string + HostIds string +} + +func (agg *hostSetAgg) toHostSet(ctx context.Context) (*HostSet, error) { + const op = "plugin.(hostSetAgg).toHostSet" + const aggregateDelimiter = "|" + const priorityDelimiter = "=" + hs := allocHostSet() + hs.PublicId = agg.PublicId + hs.CatalogId = agg.CatalogId + hs.PluginId = agg.PluginId + hs.Name = agg.Name + hs.Description = agg.Description + hs.CreateTime = agg.CreateTime + hs.UpdateTime = agg.UpdateTime + hs.LastSyncTime = agg.LastSyncTime + hs.NeedSync = agg.NeedSync + hs.SyncIntervalSeconds = agg.SyncIntervalSeconds + hs.Version = agg.Version + hs.Attributes = agg.Attributes + if agg.HostIds != "" { + hs.HostIds = strings.Split(agg.HostIds, aggregateDelimiter) + } + if agg.PreferredEndpoints != "" { + eps := strings.Split(agg.PreferredEndpoints, aggregateDelimiter) + if len(eps) > 0 { + // We want to protect against someone messing with the DB + // and not panic, so we do a bit of a dance here + var sortErr error + sort.Slice(eps, func(i, j int) bool { + epi := strings.Split(eps[i], priorityDelimiter) + if len(epi) != 2 { + sortErr = errors.New(ctx, errors.NotSpecificIntegrity, op, fmt.Sprintf("preferred endpoint %s had unexpected fields", eps[i])) + return false + } + epj := strings.Split(eps[j], priorityDelimiter) + if len(epj) != 2 { + sortErr = errors.New(ctx, errors.NotSpecificIntegrity, op, fmt.Sprintf("preferred endpoint %s had unexpected fields", eps[j])) + return false + } + indexi, err := strconv.Atoi(epi[0]) + if err != nil { + sortErr = errors.Wrap(ctx, err, op) + return false + } + indexj, err := strconv.Atoi(epj[0]) + if err != nil { + sortErr = errors.Wrap(ctx, err, op) + return false + } + return indexi < indexj + }) + if sortErr != nil { + return nil, sortErr + } + for i, ep := range eps { + // At this point they're in the correct order, but we still + // have to strip off the priority + eps[i] = strings.Split(ep, priorityDelimiter)[1] + } + hs.PreferredEndpoints = eps + } + } + return hs, nil +} + +func (agg *hostSetAgg) GetPublicId() string { + return agg.PublicId +} + +// TableName returns the table name for gorm +func (agg *hostSetAgg) TableName() string { + return "host_plugin_host_set_with_value_obj" +} diff --git a/internal/host/plugin/host_set_member.go b/internal/host/plugin/host_set_member.go new file mode 100644 index 000000000..912f148df --- /dev/null +++ b/internal/host/plugin/host_set_member.go @@ -0,0 +1,60 @@ +package plugin + +import ( + "context" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/host/plugin/store" +) + +// A HostSetMember represents the membership of a host in a host set. +type HostSetMember struct { + *store.HostSetMember + tableName string `gorm:"-"` +} + +// NewHostSetMember creates a new in memory HostSetMember representing the +// membership of hostId in hostSetId. +func NewHostSetMember(ctx context.Context, setId, hostId string, opt ...Option) (*HostSetMember, error) { + const op = "plugin.NewHostSetMember" + if setId == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "no set id") + } + if hostId == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "no host id") + } + member := &HostSetMember{ + HostSetMember: &store.HostSetMember{ + SetId: setId, + HostId: hostId, + }, + } + return member, nil +} + +// VetForWrite implements db.VetForWrite() interface for host set members. +func (m *HostSetMember) VetForWrite(ctx context.Context, _ db.Reader, _ db.OpType, _ ...db.Option) error { + const op = "plugin.(HostSetMember).VetForWrite" + if m.SetId == "" { + return errors.New(ctx, errors.InvalidParameter, op, "missing set id") + } + if m.HostId == "" { + return errors.New(ctx, errors.InvalidParameter, op, "missing host id") + } + return nil +} + +// TableName returns the table name for the host set. +func (m *HostSetMember) TableName() string { + if m.tableName != "" { + return m.tableName + } + return "host_plugin_set_member" +} + +// SetTableName sets the table name. If the caller attempts to +// set the name to "" the name will be reset to the default name. +func (m *HostSetMember) SetTableName(n string) { + m.tableName = n +} diff --git a/internal/host/plugin/host_set_member_test.go b/internal/host/plugin/host_set_member_test.go new file mode 100644 index 000000000..056552db8 --- /dev/null +++ b/internal/host/plugin/host_set_member_test.go @@ -0,0 +1,250 @@ +package plugin + +import ( + "context" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/host/plugin/store" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/oplog" + hostplg "github.com/hashicorp/boundary/internal/plugin/host" + "github.com/hashicorp/boundary/internal/scheduler" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestHostSetMember_InsertDelete tests insertion and validates the lookup via set IDs +// function as well. After, we remove set memberships one at a time and validate +// the cleanup function removes the hosts. +func TestHostSetMember_InsertDelete(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + sched := scheduler.TestScheduler(t, conn, wrapper) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + + plg := hostplg.TestPlugin(t, conn, "create") + plgm := map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): NewWrappingPluginClient(&plgpb.UnimplementedHostPluginServiceServer{}), + } + + repo, err := NewRepository(rw, rw, kms, sched, plgm) + require.NoError(t, err) + + cats := TestCatalogs(t, conn, prj.PublicId, plg.PublicId, 2) + + blueCat := cats[0] + blueSet1 := TestSet(t, conn, kms, sched, blueCat, plgm) + blueSet2 := TestSet(t, conn, kms, sched, blueCat, plgm) + blueSet3 := TestSet(t, conn, kms, sched, blueCat, plgm) + + hostId, err := db.NewPublicId(HostPrefix) + require.NoError(t, err) + blueHost1 := NewHost(ctx, blueCat.PublicId, "blue1", withPluginId(plg.GetPublicId())) + blueHost1.PublicId = hostId + require.NoError(t, rw.Create(ctx, blueHost1)) + + hostId, err = db.NewPublicId(HostPrefix) + require.NoError(t, err) + blueHost2 := NewHost(ctx, blueCat.PublicId, "blue2", withPluginId(plg.GetPublicId())) + blueHost2.PublicId = hostId + require.NoError(t, rw.Create(ctx, blueHost2)) + + hostId, err = db.NewPublicId(HostPrefix) + require.NoError(t, err) + blueHost3 := NewHost(ctx, blueCat.PublicId, "blue3", withPluginId(plg.GetPublicId())) + blueHost3.PublicId = hostId + require.NoError(t, rw.Create(ctx, blueHost3)) + + hostId, err = db.NewPublicId(HostPrefix) + require.NoError(t, err) + blueHost4 := NewHost(ctx, blueCat.PublicId, "blue4", withPluginId(plg.GetPublicId())) + blueHost4.PublicId = hostId + require.NoError(t, rw.Create(ctx, blueHost4)) + + greenCat := cats[1] + greenSet := TestSet(t, conn, kms, sched, greenCat, plgm) + + tests := []struct { + name string + set string + hosts []*Host + wantErr bool + direct bool + }{ + { + name: "valid-host-in-set", + set: blueSet1.PublicId, + hosts: []*Host{blueHost1}, + }, + { + name: "valid-other-host-in-set", + set: blueSet2.PublicId, + hosts: []*Host{blueHost2}, + }, + { + name: "valid-two-hosts-in-set", + set: blueSet3.PublicId, + hosts: []*Host{blueHost3, blueHost4}, + }, + { + name: "invalid-diff-catalogs", + set: greenSet.PublicId, + hosts: []*Host{blueHost1}, + wantErr: true, + }, + { + name: "test-vet-for-write-no-set", + hosts: []*Host{blueHost1}, + wantErr: true, + direct: true, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + var hostIds []string + for _, host := range tt.hosts { + var got *HostSetMember + var err error + hostIds = append(hostIds, host.PublicId) + if !tt.direct { + got, err = NewHostSetMember(ctx, tt.set, host.PublicId) + } else { + got = &HostSetMember{ + HostSetMember: &store.HostSetMember{ + SetId: tt.set, + }, + } + if host != nil { + got.HostId = host.PublicId + } + } + require.NoError(err) + require.NotNil(got) + err2 := rw.Create(ctx, got) + if tt.wantErr { + assert.Error(err2) + return + } + assert.NoError(err2) + } + + // Run a test on the aggregate to validate looking up sets + for _, host := range tt.hosts { + agg := &hostAgg{PublicId: host.PublicId} + require.NoError(rw.LookupByPublicId(ctx, agg)) + h := agg.toHost() + assert.ElementsMatch(h.SetIds, []string{tt.set}) + } + + set, _, err := repo.LookupSet(ctx, tt.set) + require.NoError(err) + require.NotNil(set) + require.ElementsMatch(set.HostIds, hostIds) + }) + } + + hosts, err := repo.ListHostsBySetIds(ctx, []string{blueSet1.PublicId, blueSet2.PublicId}) + require.NoError(t, err) + require.Len(t, hosts, 2) + + // Base case the count by catalog ID + hosts, _, err = repo.ListHostsByCatalogId(ctx, blueCat.PublicId) + require.NoError(t, err) + assert.Len(t, hosts, 4) + + j, err := newOrphanedHostCleanupJob(ctx, rw, rw, kms) + require.NoError(t, err) + // Delete first membership, validate host is gone + got, err := NewHostSetMember(ctx, blueSet1.PublicId, blueHost1.PublicId) + require.NoError(t, err) + require.NotNil(t, got) + num, err := rw.Delete(ctx, got) + require.NoError(t, err) + assert.Equal(t, 1, num) + count, err := j.deleteOrphanedHosts(ctx) + require.NoError(t, err) + assert.Equal(t, 1, count) + assert.NoError(t, db.TestVerifyOplog(t, rw, blueHost1.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))) + hosts, _, err = repo.ListHostsByCatalogId(ctx, blueCat.PublicId) + require.NoError(t, err) + require.Len(t, hosts, 3) + + // Delete second, validate second host is gone + got, err = NewHostSetMember(ctx, blueSet2.PublicId, blueHost2.PublicId) + require.NoError(t, err) + require.NotNil(t, got) + num, err = rw.Delete(ctx, got) + require.NoError(t, err) + assert.Equal(t, 1, num) + count, err = j.deleteOrphanedHosts(ctx) + require.NoError(t, err) + assert.Equal(t, 1, count) + assert.NoError(t, db.TestVerifyOplog(t, rw, blueHost2.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))) + hosts, _, err = repo.ListHostsByCatalogId(ctx, blueCat.PublicId) + require.NoError(t, err) + require.Len(t, hosts, 2) + + // Delete third set, validate remaining hosts are gone + gotSet, err := NewHostSet(ctx, blueCat.PublicId) + require.NoError(t, err) + require.NotNil(t, got) + gotSet.PublicId = blueSet3.PublicId + num, err = rw.Delete(ctx, gotSet) + require.NoError(t, err) + assert.Equal(t, 1, num) + count, err = j.deleteOrphanedHosts(ctx) + require.NoError(t, err) + assert.Equal(t, 2, count) + assert.NoError(t, db.TestVerifyOplog(t, rw, blueHost3.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))) + assert.NoError(t, db.TestVerifyOplog(t, rw, blueHost4.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))) + hosts, _, err = repo.ListHostsByCatalogId(ctx, blueCat.PublicId) + require.NoError(t, err) + require.Len(t, hosts, 0) +} + +func TestHostSetMember_SetTableName(t *testing.T) { + defaultTableName := "host_plugin_set_member" + tests := []struct { + name string + initialName string + setNameTo string + want string + }{ + { + name: "new-name", + initialName: "", + setNameTo: "new-name", + want: "new-name", + }, + { + name: "reset to default", + initialName: "initial", + setNameTo: "", + want: defaultTableName, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + def := &HostSetMember{ + HostSetMember: &store.HostSetMember{}, + } + require.Equal(defaultTableName, def.TableName()) + s := &HostSetMember{ + HostSetMember: &store.HostSetMember{}, + tableName: tt.initialName, + } + s.SetTableName(tt.setNameTo) + assert.Equal(tt.want, s.TableName()) + }) + } +} diff --git a/internal/host/plugin/host_set_test.go b/internal/host/plugin/host_set_test.go new file mode 100644 index 000000000..ae9144cd5 --- /dev/null +++ b/internal/host/plugin/host_set_test.go @@ -0,0 +1,224 @@ +package plugin + +import ( + "context" + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/host/plugin/store" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/plugin/host" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/structpb" +) + +func TestHostSet_Create(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + plg := host.TestPlugin(t, conn, "test") + cat := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + cat2 := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + + type args struct { + catalogId string + opts []Option + } + + tests := []struct { + name string + args args + want *HostSet + wantErr bool + }{ + { + name: "blank-catalogId", + args: args{ + catalogId: "", + }, + want: &HostSet{HostSet: &store.HostSet{ + Attributes: []byte{}, + }}, + wantErr: true, + }, + { + name: "valid-no-options", + args: args{ + catalogId: cat.GetPublicId(), + }, + want: &HostSet{ + HostSet: &store.HostSet{ + CatalogId: cat.GetPublicId(), + Attributes: []byte{}, + }, + }, + }, + { + name: "valid-with-name", + args: args{ + catalogId: cat.GetPublicId(), + opts: []Option{ + WithName("test-name"), + }, + }, + want: &HostSet{ + HostSet: &store.HostSet{ + CatalogId: cat.GetPublicId(), + Name: "test-name", + Attributes: []byte{}, + }, + }, + }, + { + name: "duplicate-name", + args: args{ + catalogId: cat.GetPublicId(), + opts: []Option{ + WithName("test-name"), + }, + }, + want: &HostSet{ + HostSet: &store.HostSet{ + CatalogId: cat.GetPublicId(), + Name: "test-name", + Attributes: []byte{}, + }, + }, + wantErr: true, + }, + { + name: "valid-duplicate-name-different-catalog", + args: args{ + catalogId: cat2.GetPublicId(), + opts: []Option{ + WithName("test-name"), + }, + }, + want: &HostSet{ + HostSet: &store.HostSet{ + CatalogId: cat2.GetPublicId(), + Name: "test-name", + Attributes: []byte{}, + }, + }, + }, + { + name: "valid-with-description", + args: args{ + catalogId: cat.GetPublicId(), + opts: []Option{ + WithDescription("test-description"), + }, + }, + want: &HostSet{ + HostSet: &store.HostSet{ + CatalogId: cat.GetPublicId(), + Description: "test-description", + Attributes: []byte{}, + }, + }, + }, + { + name: "valid-with-attributes", + args: args{ + catalogId: cat.GetPublicId(), + opts: []Option{ + WithAttributes(&structpb.Struct{Fields: map[string]*structpb.Value{"foo": structpb.NewStringValue("bar")}}), + }, + }, + want: &HostSet{ + HostSet: &store.HostSet{ + CatalogId: cat.GetPublicId(), + Attributes: func() []byte { + st := &structpb.Struct{Fields: map[string]*structpb.Value{"foo": structpb.NewStringValue("bar")}} + b, err := proto.Marshal(st) + require.NoError(t, err) + return b + }(), + }, + }, + }, + { + name: "valid-with-preferred-endpoints", + args: args{ + catalogId: cat.GetPublicId(), + opts: []Option{ + WithPreferredEndpoints([]string{"cidr:1.2.3.4"}), + }, + }, + want: &HostSet{ + HostSet: &store.HostSet{ + CatalogId: cat.GetPublicId(), + Attributes: []byte{}, + }, + PreferredEndpoints: []string{"cidr:1.2.3.4"}, + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + got, err := NewHostSet(ctx, tt.args.catalogId, tt.args.opts...) + require.NoError(t, err) + require.NotNil(t, got) + assert.Emptyf(t, got.PublicId, "PublicId set") + assert.Equal(t, tt.want, got) + + id, err := newHostSetId(ctx) + assert.NoError(t, err) + + tt.want.PublicId = id + got.PublicId = id + + w := db.New(conn) + err = w.Create(context.Background(), got) + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestHostSet_SetTableName(t *testing.T) { + defaultTableName := "host_plugin_set" + tests := []struct { + name string + initialName string + setNameTo string + want string + }{ + { + name: "new-name", + initialName: "", + setNameTo: "new-name", + want: "new-name", + }, + { + name: "reset to default", + initialName: "initial", + setNameTo: "", + want: defaultTableName, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + def := &HostSet{ + HostSet: &store.HostSet{}, + } + require.Equal(defaultTableName, def.TableName()) + s := &HostSet{ + HostSet: &store.HostSet{}, + tableName: tt.initialName, + } + s.SetTableName(tt.setNameTo) + assert.Equal(tt.want, s.TableName()) + }) + } +} diff --git a/internal/host/plugin/host_test.go b/internal/host/plugin/host_test.go new file mode 100644 index 000000000..a930a6692 --- /dev/null +++ b/internal/host/plugin/host_test.go @@ -0,0 +1,204 @@ +package plugin + +import ( + "context" + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/host/plugin/store" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/plugin/host" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestHost_Create(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + plg := host.TestPlugin(t, conn, "test") + cat := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + cat2 := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + + type args struct { + catalogId string + externalId string + opts []Option + } + + tests := []struct { + name string + args args + want *Host + wantErr bool + }{ + { + name: "blank-catalogId", + args: args{ + catalogId: "", + externalId: "external_id", + }, + want: &Host{Host: &store.Host{ + ExternalId: "external_id", + }}, + wantErr: true, + }, + { + name: "blank-external-id", + args: args{ + catalogId: cat.GetPublicId(), + }, + want: &Host{Host: &store.Host{ + CatalogId: cat.GetPublicId(), + }}, + wantErr: true, + }, + { + name: "valid-no-options", + args: args{ + catalogId: cat.GetPublicId(), + externalId: "valid-no-options", + }, + want: &Host{ + Host: &store.Host{ + CatalogId: cat.GetPublicId(), + ExternalId: "valid-no-options", + }, + }, + }, + { + name: "valid-with-name", + args: args{ + catalogId: cat.GetPublicId(), + externalId: "valid-with-name", + opts: []Option{ + WithName("test-name"), + }, + }, + want: &Host{ + Host: &store.Host{ + CatalogId: cat.GetPublicId(), + ExternalId: "valid-with-name", + Name: "test-name", + }, + }, + }, + { + name: "valid-duplicate-name-same-catalog", + args: args{ + catalogId: cat.GetPublicId(), + externalId: "duplicate-name", + opts: []Option{ + WithName("test-name"), + }, + }, + want: &Host{ + Host: &store.Host{ + CatalogId: cat.GetPublicId(), + ExternalId: "duplicate-name", + Name: "test-name", + }, + }, + }, + { + name: "valid-duplicate-name-different-catalog", + args: args{ + catalogId: cat2.GetPublicId(), + externalId: "valid-duplicate-name-different-catalog", + opts: []Option{ + WithName("test-name"), + }, + }, + want: &Host{ + Host: &store.Host{ + CatalogId: cat2.GetPublicId(), + ExternalId: "valid-duplicate-name-different-catalog", + Name: "test-name", + }, + }, + }, + { + name: "valid-with-description", + args: args{ + catalogId: cat.GetPublicId(), + externalId: "valid-with-description", + opts: []Option{ + WithDescription("test-description"), + }, + }, + want: &Host{ + Host: &store.Host{ + CatalogId: cat.GetPublicId(), + ExternalId: "valid-with-description", + Description: "test-description", + }, + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + got := NewHost(ctx, tt.args.catalogId, tt.args.externalId, tt.args.opts...) + require.NotNil(t, got) + assert.Emptyf(t, got.PublicId, "PublicId set") + assert.Equal(t, tt.want, got) + + id, err := newHostId(ctx, tt.name, tt.name) + assert.NoError(t, err) + + tt.want.PublicId = id + got.PublicId = id + + w := db.New(conn) + err = w.Create(context.Background(), got) + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +// TODO: Test Deletion directly of host, of set membership, and of cascading +// from the set + +func TestHost_SetTableName(t *testing.T) { + defaultTableName := "host_plugin_host" + tests := []struct { + name string + initialName string + setNameTo string + want string + }{ + { + name: "new-name", + initialName: "", + setNameTo: "new-name", + want: "new-name", + }, + { + name: "reset to default", + initialName: "initial", + setNameTo: "", + want: defaultTableName, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + def := &Host{ + Host: &store.Host{}, + } + require.Equal(defaultTableName, def.TableName()) + s := &Host{ + Host: &store.Host{}, + tableName: tt.initialName, + } + s.SetTableName(tt.setNameTo) + assert.Equal(tt.want, s.TableName()) + }) + } +} diff --git a/internal/host/plugin/ids.go b/internal/host/plugin/ids.go new file mode 100644 index 000000000..ab8e77e4e --- /dev/null +++ b/internal/host/plugin/ids.go @@ -0,0 +1,56 @@ +package plugin + +import ( + "context" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/host" + "github.com/hashicorp/boundary/internal/types/subtypes" +) + +func init() { + if err := host.Register(Subtype, HostCatalogPrefix, HostSetPrefix, HostPrefix); err != nil { + panic(err) + } +} + +// PublicId prefixes for the resources in the plugin package. +const ( + HostCatalogPrefix = "hc" + HostSetPrefix = "hs" + HostPrefix = "h" + + Subtype = subtypes.Subtype("plugin") +) + +func newHostCatalogId(ctx context.Context) (string, error) { + id, err := db.NewPublicId(HostCatalogPrefix) + if err != nil { + return "", errors.Wrap(ctx, err, "plugin.newHostCatalogId") + } + return id, nil +} + +func newHostSetId(ctx context.Context) (string, error) { + id, err := db.NewPublicId(HostSetPrefix) + if err != nil { + return "", errors.Wrap(ctx, err, "plugin.newHostSetId") + } + return id, nil +} + +func newHostId(ctx context.Context, catalogId, externalId string) (string, error) { + const op = "plugin.newHostId" + if catalogId == "" { + return "", errors.New(ctx, errors.InvalidParameter, op, "missing catalog id") + } + if externalId == "" { + return "", errors.New(ctx, errors.InvalidParameter, op, "missing external id") + } + id, err := db.NewPublicId(HostPrefix, db.WithPrngValues([]string{catalogId, externalId})) + if err != nil { + return "", errors.Wrap(ctx, err, op) + } + return id, nil +} diff --git a/internal/host/plugin/immutable_fields_test.go b/internal/host/plugin/immutable_fields_test.go new file mode 100644 index 000000000..924a8110c --- /dev/null +++ b/internal/host/plugin/immutable_fields_test.go @@ -0,0 +1,170 @@ +package plugin + +import ( + "context" + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/db/timestamp" + "github.com/hashicorp/boundary/internal/host/plugin/store" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/plugin/host" + "github.com/hashicorp/boundary/internal/scheduler" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func TestPluginCatalog_ImmutableFields(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + w := db.New(conn) + wrapper := db.TestWrapper(t) + ts := timestamp.Timestamp{Timestamp: ×tamppb.Timestamp{Seconds: 0, Nanos: 0}} + o, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + plg := host.TestPlugin(t, conn, "test") + plg2 := host.TestPlugin(t, conn, "test2") + new := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + + tests := []struct { + name string + update *HostCatalog + fieldMask []string + }{ + { + name: "public_id", + update: func() *HostCatalog { + c := new.clone() + c.PublicId = "hc_thisIsNotAValidId" + return c + }(), + fieldMask: []string{"PublicId"}, + }, + { + name: "create time", + update: func() *HostCatalog { + c := new.clone() + c.CreateTime = &ts + return c + }(), + fieldMask: []string{"CreateTime"}, + }, + { + name: "scope id", + update: func() *HostCatalog { + c := new.clone() + c.ScopeId = o.PublicId + return c + }(), + fieldMask: []string{"ScopeId"}, + }, + { + name: "plugin id", + update: func() *HostCatalog { + c := new.clone() + c.PluginId = plg2.GetPublicId() + return c + }(), + fieldMask: []string{"PluginId"}, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + orig := new.clone() + err := w.LookupById(context.Background(), orig) + require.NoError(err) + + rowsUpdated, err := w.Update(context.Background(), tt.update, tt.fieldMask, nil, db.WithSkipVetForWrite(true)) + require.Error(err) + assert.Equal(0, rowsUpdated) + + after := new.clone() + err = w.LookupById(context.Background(), after) + require.NoError(err) + + assert.True(proto.Equal(orig, after)) + }) + } +} + +func TestPluginSet_ImmutableFields(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + w := db.New(conn) + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + sched := scheduler.TestScheduler(t, conn, wrapper) + + ts := timestamp.Timestamp{Timestamp: ×tamppb.Timestamp{Seconds: 0, Nanos: 0}} + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + plg := host.TestPlugin(t, conn, "test") + cat := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + new := TestSet(t, conn, kmsCache, sched, cat, map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): NewWrappingPluginClient(&TestPluginServer{}), + }) + + tests := []struct { + name string + update *HostSet + fieldMask []string + }{ + { + name: "public_id", + update: func() *HostSet { + c := new.testCloneHostSet() + c.PublicId = "hc_thisIsNotAValidId" + return c + }(), + fieldMask: []string{"PublicId"}, + }, + { + name: "create time", + update: func() *HostSet { + c := new.testCloneHostSet() + c.CreateTime = &ts + return c + }(), + fieldMask: []string{"CreateTime"}, + }, + { + name: "host_plugin_catalog_id", + update: func() *HostSet { + c := new.testCloneHostSet() + c.CatalogId = "stc_01234567890" + return c + }(), + fieldMask: []string{"CatalogId"}, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + orig := new.testCloneHostSet() + err := w.LookupById(context.Background(), orig) + require.NoError(err) + + rowsUpdated, err := w.Update(context.Background(), tt.update, tt.fieldMask, nil, db.WithSkipVetForWrite(true)) + require.Error(err) + assert.Equal(0, rowsUpdated) + + after := new.testCloneHostSet() + err = w.LookupById(context.Background(), after) + require.NoError(err) + + assert.True(proto.Equal(orig, after)) + }) + } +} + +func (c *HostSet) testCloneHostSet() *HostSet { + cp := proto.Clone(c.HostSet) + return &HostSet{ + HostSet: cp.(*store.HostSet), + } +} diff --git a/internal/host/plugin/job_orphaned_host_cleanup.go b/internal/host/plugin/job_orphaned_host_cleanup.go new file mode 100644 index 000000000..ed521f4b0 --- /dev/null +++ b/internal/host/plugin/job_orphaned_host_cleanup.go @@ -0,0 +1,158 @@ +package plugin + +import ( + "context" + "time" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/oplog" + "github.com/hashicorp/boundary/internal/scheduler" + ua "go.uber.org/atomic" +) + +const ( + orphanedHostCleanupJobName = "plugin_host_orpahend_hosts_cleanup" + orphanedHostCleanupJobRunInterval = 5 * time.Minute +) + +// OrphanedHostCleanupJob is the recurring job that syncs hosts from sets that are. +// The OrphanedHostCleanupJob is not thread safe, +// an attempt to Run the job concurrently will result in an JobAlreadyRunning error. +type OrphanedHostCleanupJob struct { + reader db.Reader + writer db.Writer + kms *kms.Kms + limit int + + running ua.Bool + numHosts int + numProcessed int +} + +// newOrphanedHostCleanupJob creates a new in-memory OrphanedHostCleanupJob. +func newOrphanedHostCleanupJob(ctx context.Context, r db.Reader, w db.Writer, kms *kms.Kms, _ ...Option) (*OrphanedHostCleanupJob, error) { + const op = "plugin.newOrphanedHostCleanupJob" + switch { + case r == nil: + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing db.Reader") + case w == nil: + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing db.Writer") + case kms == nil: + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing kms") + } + + return &OrphanedHostCleanupJob{ + reader: r, + writer: w, + kms: kms, + }, nil +} + +// Status returns the current status of the set sync job. Total is the total number +// of sets that are to be synced. Completed is the number of sets already synced. +func (r *OrphanedHostCleanupJob) Status() scheduler.JobStatus { + return scheduler.JobStatus{ + Completed: r.numProcessed, + Total: r.numHosts, + } +} + +// Run queries the plugin host repo hosts with no memberships, it then +// deletes those hosts. Can not be run in parallel, if +// Run is invoked while already running an error with code JobAlreadyRunning +// will be returned. +func (r *OrphanedHostCleanupJob) Run(ctx context.Context) error { + const op = "plugin.(OrphanedHostCleanupJob).Run" + if !r.running.CAS(r.running.Load(), true) { + return errors.New(ctx, errors.JobAlreadyRunning, op, "job already running") + } + defer r.running.Store(false) + + // Verify context is not done before running + if err := ctx.Err(); err != nil { + return errors.Wrap(ctx, err, op) + } + + i, err := r.deleteOrphanedHosts(ctx) + if err != nil { + return errors.Wrap(ctx, err, op) + } + + // Set numProcessed and numHosts for status report + r.numProcessed, r.numHosts = i, i + return nil +} + +// NextRunIn returns the default run frequency of the cleanup job. +func (r *OrphanedHostCleanupJob) NextRunIn() (time.Duration, error) { + const op = "plugin.(OrphanedHostCleanupJob).NextRunIn" + return orphanedHostCleanupJobRunInterval, nil +} + +// Name is the unique name of the job. +func (r *OrphanedHostCleanupJob) Name() string { + return orphanedHostCleanupJobName +} + +// Description is the human readable description of the job. +func (r *OrphanedHostCleanupJob) Description() string { + return "Periodically deletes plugin based hosts which are not members of any sets." +} + +// deleteOrphanedHosts deletes any hosts that no longer belong to any set. +// WithLimit is the only option supported. No options are currently supported. +func (r *OrphanedHostCleanupJob) deleteOrphanedHosts(ctx context.Context, _ ...Option) (int, error) { + const op = "plugin.(OrphanedHostCleanupJob).deleteOrphanedHosts" + + query := ` +public_id in + (select public_id + from host_plugin_host + where public_id not in + (select host_id from host_plugin_set_member)) +` + + var hostAggs []*hostAgg + err := r.reader.SearchWhere(ctx, &hostAggs, query, nil) + switch { + case err != nil: + return db.NoRowsAffected, errors.Wrap(ctx, err, op) + case len(hostAggs) == 0: + return db.NoRowsAffected, nil + } + + scopeToHost := make(map[string][]*Host) + for _, ha := range hostAggs { + h := allocHost() + h.PublicId = ha.PublicId + scopeToHost[ha.ScopeId] = append(scopeToHost[ha.ScopeId], h) + } + + _, err = r.writer.DoTx( + ctx, + db.StdRetryCnt, + db.ExpBackoff{}, + func(_ db.Reader, w db.Writer) error { + for scopeId, hosts := range scopeToHost { + oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) + } + for _, h := range hosts { + metadata := h.oplog(oplog.OpType_OP_TYPE_DELETE) + dHost := h.clone() + if _, err := w.Delete(ctx, dHost, db.WithOplog(oplogWrapper, metadata)); err != nil { + return errors.Wrap(ctx, err, op) + } + } + } + return nil + }) + if err != nil { + return db.NoRowsAffected, errors.Wrap(ctx, err, op) + } + + return len(hostAggs), nil +} diff --git a/internal/host/plugin/job_orphaned_host_cleanup_test.go b/internal/host/plugin/job_orphaned_host_cleanup_test.go new file mode 100644 index 000000000..3b36be2fb --- /dev/null +++ b/internal/host/plugin/job_orphaned_host_cleanup_test.go @@ -0,0 +1,161 @@ +package plugin + +import ( + "context" + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + hostplg "github.com/hashicorp/boundary/internal/plugin/host" + "github.com/hashicorp/boundary/internal/scheduler" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewOrphanedHostCleanupJob(t *testing.T) { + t.Parallel() + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + + type args struct { + r db.Reader + w db.Writer + kms *kms.Kms + } + tests := []struct { + name string + args args + options []Option + wantErr bool + wantErrCode errors.Code + }{ + { + name: "nil reader", + args: args{ + w: rw, + kms: kmsCache, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "nil writer", + args: args{ + r: rw, + kms: kmsCache, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "nil kms", + args: args{ + r: rw, + w: rw, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "valid", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + got, err := newOrphanedHostCleanupJob(ctx, tt.args.r, tt.args.w, tt.args.kms) + if tt.wantErr { + require.Error(err) + assert.Nil(got) + assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "Unexpected error %s", err) + return + } + require.NoError(err) + require.NotNil(got) + assert.Equal(tt.args.r, got.reader) + assert.Equal(tt.args.w, got.writer) + assert.Equal(tt.args.kms, got.kms) + }) + } +} + +func TestOrphanedHostCleanupJob_Run(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + sched := scheduler.TestScheduler(t, conn, wrapper) + + plgServer := &TestPluginServer{} + plg := hostplg.TestPlugin(t, conn, "run") + plgm := map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): NewWrappingPluginClient(plgServer), + } + + r, err := newOrphanedHostCleanupJob(ctx, rw, rw, kmsCache) + require.NoError(err) + sche := scheduler.TestScheduler(t, conn, wrapper) + err = sche.RegisterJob(context.Background(), r) + require.NoError(err) + + err = r.Run(context.Background()) + require.NoError(err) + // No sets should have been synced. + assert.Equal(0, r.numProcessed) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + + cat := TestCatalog(t, conn, prj.GetPublicId(), plg.GetPublicId()) + set1 := TestSet(t, conn, kmsCache, sched, cat, plgm) + host1 := TestHost(t, conn, cat.GetPublicId(), "host with membership") + TestSetMembers(t, conn, set1.GetPublicId(), []*Host{host1}) + + // 1 host with no set membership + TestHost(t, conn, cat.GetPublicId(), "host2") + + // Run sync again with the newly created set + err = r.Run(context.Background()) + require.NoError(err) + // The single existing set should have been processed + assert.Equal(1, r.numHosts) + assert.Equal(1, r.numProcessed) + + // 5 host with no set membership + TestHost(t, conn, cat.GetPublicId(), "1") + TestHost(t, conn, cat.GetPublicId(), "2") + TestHost(t, conn, cat.GetPublicId(), "3") + TestHost(t, conn, cat.GetPublicId(), "4") + TestHost(t, conn, cat.GetPublicId(), "5") + + // Run sync again with the freshly synced set + err = r.Run(context.Background()) + require.NoError(err) + // The single existing set should have been processed + assert.Equal(5, r.numHosts) + assert.Equal(5, r.numProcessed) + + // Run sync again with the freshly synced set + err = r.Run(context.Background()) + require.NoError(err) + // The single existing set should have been processed + assert.Equal(0, r.numHosts) + assert.Equal(0, r.numProcessed) + + require.NoError(rw.LookupById(ctx, host1)) +} diff --git a/internal/host/plugin/job_set_sync.go b/internal/host/plugin/job_set_sync.go new file mode 100644 index 000000000..1b63da46c --- /dev/null +++ b/internal/host/plugin/job_set_sync.go @@ -0,0 +1,559 @@ +package plugin + +import ( + "context" + "fmt" + "time" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/libs/endpoint" + "github.com/hashicorp/boundary/internal/oplog" + "github.com/hashicorp/boundary/internal/scheduler" + hcpb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs" + pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" + ua "go.uber.org/atomic" +) + +const ( + setSyncJobName = "plugin_host_set_sync" + setSyncJobRunInterval = 10 * time.Minute +) + +// SetSyncJob is the recurring job that syncs hosts from sets that are. +// The SetSyncJob is not thread safe, +// an attempt to Run the job concurrently will result in an JobAlreadyRunning error. +type SetSyncJob struct { + reader db.Reader + writer db.Writer + kms *kms.Kms + plugins map[string]plgpb.HostPluginServiceClient + limit int + + running ua.Bool + numSets int + numProcessed int +} + +// newSetSyncJob creates a new in-memory SetSyncJob. +// +// WithLimit is the only supported option. +func newSetSyncJob(ctx context.Context, r db.Reader, w db.Writer, kms *kms.Kms, plgm map[string]plgpb.HostPluginServiceClient, opt ...Option) (*SetSyncJob, error) { + const op = "plugin.newSetSyncJob" + switch { + case r == nil: + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing db.Reader") + case w == nil: + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing db.Writer") + case kms == nil: + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing kms") + case len(plgm) == 0: + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing plugin manager") + } + + opts := getOpts(opt...) + if opts.withLimit == 0 { + // zero signals the boundary defaults should be used. + opts.withLimit = db.DefaultLimit + } + return &SetSyncJob{ + reader: r, + writer: w, + kms: kms, + plugins: plgm, + limit: opts.withLimit, + }, nil +} + +// Status returns the current status of the set sync job. Total is the total number +// of sets that are to be synced. Completed is the number of sets already synced. +func (r *SetSyncJob) Status() scheduler.JobStatus { + return scheduler.JobStatus{ + Completed: r.numProcessed, + Total: r.numSets, + } +} + +// Run queries the plugin host repo for sets that need to be synced, it then +// creates a plugin client and syncs each set. Can not be run in parallel, if +// Run is invoked while already running an error with code JobAlreadyRunning +// will be returned. +func (r *SetSyncJob) Run(ctx context.Context) error { + const op = "plugin.(SetSyncJob).Run" + if !r.running.CAS(r.running.Load(), true) { + return errors.New(ctx, errors.JobAlreadyRunning, op, "job already running") + } + defer r.running.Store(false) + + // Verify context is not done before running + if err := ctx.Err(); err != nil { + return errors.Wrap(ctx, err, op) + } + + var setAggs []*hostSetAgg + // Fetch all sets that will reach their sync point within the syncWindow. + // This is done to avoid constantly scheduling the set sync job when there + // are multiple sets to sync in sequence. + err := r.reader.SearchWhere(ctx, &setAggs, setSyncJobQuery, []interface{}{-1 * setSyncJobRunInterval.Seconds()}, db.WithLimit(r.limit)) + if err != nil { + return errors.Wrap(ctx, err, op) + } + + // Set numProcessed and numHosts for status report + r.numProcessed, r.numSets = 0, len(setAggs) + if len(setAggs) == 0 { + return nil + } + + setIds := make([]string, 0, len(setAggs)) + for _, sa := range setAggs { + setIds = append(setIds, sa.PublicId) + } + return r.syncSets(ctx, setIds) +} + +// NextRunIn queries the plugin host set db to determine when the next set should be synced. +func (r *SetSyncJob) NextRunIn() (time.Duration, error) { + const op = "plugin.(SetSyncJob).NextRunIn" + next, err := nextSync(r) + if err != nil { + return setSyncJobRunInterval, errors.WrapDeprecated(err, op) + } + return next, nil +} + +// Name is the unique name of the job. +func (r *SetSyncJob) Name() string { + return setSyncJobName +} + +// Description is the human readable description of the job. +func (r *SetSyncJob) Description() string { + return "Periodically syncs plugin based catalog hosts and host set memberships." +} + +func nextSync(j scheduler.Job) (time.Duration, error) { + const op = "plugin.nextSync" + var query string + var r db.Reader + switch job := j.(type) { + case *SetSyncJob: + query = setSyncNextRunInQuery + r = job.reader + default: + return 0, errors.NewDeprecated(errors.Unknown, op, "unknown job") + } + + rows, err := r.Query(context.Background(), query, []interface{}{setSyncJobRunInterval}) + if err != nil { + return 0, errors.WrapDeprecated(err, op) + } + defer rows.Close() + + if !rows.Next() { + return setSyncJobRunInterval, nil + } + + type NextResync struct { + SyncNow bool + SyncIntervalSeconds int32 + ResyncIn time.Duration + } + var n NextResync + err = r.ScanRows(rows, &n) + if err != nil { + return 0, errors.WrapDeprecated(err, op) + } + switch { + case n.SyncNow: + // Immediate + return 0, nil + case n.SyncIntervalSeconds < 0: + // In this case automatic syncing is disabled; we still sync if SyncNow + // but otherwise do not. We schedule the job at the default cadence but + // it will do nothing, just calculate a next run time to ensure it + // should stay disabled. + return setSyncJobRunInterval, nil + case n.ResyncIn < 0: + // Immediate + return 0, nil + } + return n.ResyncIn * time.Second, nil +} + +// syncSets retrieves from their plugins all the host and membership information +// for the provided host sets and updates their values in the database. +func (r *SetSyncJob) syncSets(ctx context.Context, setIds []string) error { + const op = "plugin.(SetSyncJob).syncSets" + if len(setIds) == 0 { + return errors.New(ctx, errors.InvalidParameter, op, "no set ids") + } + + // Fist, look up the sets corresponding to the set IDs + var setAggs []*hostSetAgg + if err := r.reader.SearchWhere(ctx, &setAggs, "public_id in (?)", []interface{}{setIds}); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("can't retrieve sets %v", setIds))) + } + if len(setAggs) == 0 { + return nil + } + + type setInfo struct { + preferredEndpoint endpoint.Option + plgSet *pb.HostSet + } + + type catalogInfo struct { + publicId string // ID of the catalog + plg plgpb.HostPluginServiceClient // plugin client for the catalog + setInfos map[string]*setInfo // map of set IDs to set information + plgCat *hcpb.HostCatalog // plugin host catalog + storeCat *HostCatalog + persisted *plgpb.HostCatalogPersisted // host catalog persisted (secret) data + } + + // Next, look up the distinct catalog info and assign set infos to it. + // Notably, this does not include persisted info. + catalogInfos := make(map[string]*catalogInfo) + for _, ag := range setAggs { + ci, ok := catalogInfos[ag.CatalogId] + if !ok { + ci = &catalogInfo{ + publicId: ag.CatalogId, + setInfos: make(map[string]*setInfo), + } + } + ci.plg, ok = r.plugins[ag.PluginId] + if !ok { + return errors.New(ctx, errors.Internal, op, fmt.Sprintf("expected plugin %q not available", ag.PluginId)) + } + + s, err := ag.toHostSet(ctx) + if err != nil { + return errors.Wrap(ctx, err, op) + } + si, ok := ci.setInfos[s.GetPublicId()] + if !ok { + si = &setInfo{} + } + si.preferredEndpoint = endpoint.WithPreferenceOrder(s.PreferredEndpoints) + si.plgSet, err = toPluginSet(ctx, s) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("converting set %q to plugin set", s.GetPublicId()))) + } + + ci.setInfos[s.GetPublicId()] = si + catalogInfos[ag.CatalogId] = ci + } + + // Now, look up the catalog persisted (secret) information + catIds := make([]string, 0, len(catalogInfos)) + for k := range catalogInfos { + catIds = append(catIds, k) + } + var catAggs []*catalogAgg + if err := r.reader.SearchWhere(ctx, &catAggs, "public_id in (?)", []interface{}{catIds}); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("can't retrieve catalogs %v", catIds))) + } + if len(catAggs) == 0 { + return errors.New(ctx, errors.NotSpecificIntegrity, op, "no catalogs returned for retrieved sets") + } + for _, ca := range catAggs { + c, s := ca.toCatalogAndPersisted() + ci, ok := catalogInfos[c.GetPublicId()] + if !ok { + return errors.New(ctx, errors.NotSpecificIntegrity, op, "catalog returned when no set requested it") + } + plgCat, err := toPluginCatalog(ctx, c) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("storage to plugin catalog conversion")) + } + ci.plgCat = plgCat + ci.storeCat = c + + per, err := toPluginPersistedData(ctx, r.kms, c.GetScopeId(), s) + if err != nil { + return errors.Wrap(ctx, err, op) + } + ci.persisted = per + catalogInfos[c.GetPublicId()] = ci + } + + // For each distinct catalog, list all sets at once + for _, ci := range catalogInfos { + var sets []*pb.HostSet + var catSetIds []string + for id, si := range ci.setInfos { + sets = append(sets, si.plgSet) + catSetIds = append(catSetIds, id) + } + + resp, err := ci.plg.ListHosts(ctx, &plgpb.ListHostsRequest{ + Catalog: ci.plgCat, + Sets: sets, + Persisted: ci.persisted, + }) + if err != nil { + return errors.Wrap(ctx, err, op) + } + + if _, err := r.upsertHosts(ctx, ci.storeCat, catSetIds, resp.GetHosts()); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("upserting hosts")) + } + + updateSyncDataQuery := ` +update host_plugin_set +set + last_sync_time = current_timestamp, + need_sync = false +where public_id in (?) +` + + // update last sync time on the sets + i, err := r.writer.Exec(ctx, updateSyncDataQuery, []interface{}{catSetIds}) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("updating last sync time")) + } + if i != len(catSetIds) { + return errors.New(ctx, errors.Internal, op, "mismatched number of sets updated and synced") + } + r.numProcessed += len(catSetIds) + } + return nil +} + +// upsertHosts inserts phs into the repository or updates its current +// attributes/set memberships and returns Hosts. h is not changed. hc must +// contain a valid public ID and scope ID. Each ph in phs must not contain a +// PublicId but must contain an external ID. The PublicId is generated and +// assigned by this method. +// +// NOTE: If phs is empty, this assumes that there are simply no hosts that +// matched the given sets! Which means it will remove all hosts from the given +// sets. +func (r *SetSyncJob) upsertHosts( + ctx context.Context, + hc *HostCatalog, + setIds []string, + phs []*plgpb.ListHostsResponseHost, + _ ...Option) ([]*Host, error) { + const op = "plugin.(SetSyncJob).upsertHosts" + if phs == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "nil plugin hosts") + } + for _, ph := range phs { + if ph.GetExternalId() == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing host external id") + } + } + if hc == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "nil host catalog") + } + if hc.GetPublicId() == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "no catalog id") + } + if hc.GetScopeId() == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "no scope id") + } + if setIds == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "nil sets") + } + if len(setIds) == 0 { // At least one must have been given to the plugin + return nil, errors.New(ctx, errors.InvalidParameter, op, "empty sets") + } + + oplogWrapper, err := r.kms.GetWrapper(ctx, hc.GetScopeId(), kms.KeyPurposeOplog) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) + } + + // First, fetch existing hosts for the set IDs passed in, and organize them + // into a lookup map by host ID for later usage + var currentHosts []*Host + var currentHostMap map[string]*Host + { + var err error + currentHosts, err = listHostBySetIds(ctx, r.reader, setIds) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("looking up current hosts for returned sets")) + } + + currentHostMap = make(map[string]*Host, len(currentHosts)) + for _, h := range currentHosts { + currentHostMap[h.PublicId] = h + } + } + + // Now, parse the externally defined hosts into hostInfo values, which + // stores info useful for later comparisons + newHostMap, err := createNewHostMap(ctx, hc, phs, currentHostMap) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create new host map")) + } + + var returnedHosts []*Host + // Iterate over hosts and add or update them + for _, hi := range newHostMap { + ret := hi.h.clone() + + if !hi.dirtyHost && + len(hi.ipsToAdd) == 0 && + len(hi.ipsToRemove) == 0 && + len(hi.dnsNamesToAdd) == 0 && + len(hi.dnsNamesToRemove) == 0 { + returnedHosts = append(returnedHosts, ret) + continue + } + _, err = r.writer.DoTx( + ctx, + db.StdRetryCnt, + db.ExpBackoff{}, + func(r db.Reader, w db.Writer) error { + msgs := make([]*oplog.Message, 0) + ticket, err := w.GetTicket(hi.h) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket")) + } + + if hi.dirtyHost { + var hOplogMsg oplog.Message + onConflict := &db.OnConflict{ + Target: db.Constraint("host_plugin_host_pkey"), + Action: db.SetColumns([]string{"name", "description"}), + } + if err := w.Create(ctx, ret, db.NewOplogMsg(&hOplogMsg), db.WithOnConflict(onConflict)); err != nil { + return errors.Wrap(ctx, err, op) + } + msgs = append(msgs, &hOplogMsg) + } + + // IP handling + { + if len(hi.ipsToRemove) > 0 { + oplogMsgs := make([]*oplog.Message, 0, len(hi.ipsToRemove)) + count, err := w.DeleteItems(ctx, hi.ipsToRemove.toArray(), db.NewOplogMsgs(&oplogMsgs)) + if err != nil { + return err + } + if count != len(hi.ipsToRemove) { + return errors.New(ctx, errors.UnexpectedRowsAffected, op, fmt.Sprintf("expected to remove %d ips from host %s, removed %d", len(hi.ipsToRemove), hi.h.PublicId, count)) + } + msgs = append(msgs, oplogMsgs...) + } + if len(hi.ipsToAdd) > 0 { + oplogMsgs := make([]*oplog.Message, 0, len(hi.ipsToAdd)) + if err := w.CreateItems(ctx, hi.ipsToAdd.toArray(), db.NewOplogMsgs(&oplogMsgs)); err != nil { + return err + } + msgs = append(msgs, oplogMsgs...) + } + } + + // DNS handling + { + if len(hi.dnsNamesToRemove) > 0 { + oplogMsgs := make([]*oplog.Message, 0, len(hi.dnsNamesToRemove)) + count, err := w.DeleteItems(ctx, hi.dnsNamesToRemove.toArray(), db.NewOplogMsgs(&oplogMsgs)) + if err != nil { + return err + } + if count != len(hi.dnsNamesToRemove) { + return errors.New(ctx, errors.UnexpectedRowsAffected, op, fmt.Sprintf("expected to remove %d dns names from host %s, removed %d", len(hi.dnsNamesToRemove), hi.h.PublicId, count)) + } + msgs = append(msgs, oplogMsgs...) + } + if len(hi.dnsNamesToAdd) > 0 { + oplogMsgs := make([]*oplog.Message, 0, len(hi.dnsNamesToAdd)) + if err := w.CreateItems(ctx, hi.dnsNamesToAdd.toArray(), db.NewOplogMsgs(&oplogMsgs)); err != nil { + return err + } + msgs = append(msgs, oplogMsgs...) + } + } + + metadata := hi.h.oplog(oplog.OpType_OP_TYPE_UPDATE) + if err := w.WriteOplogEntryWith(ctx, oplogWrapper, ticket, metadata, msgs); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog")) + } + + returnedHosts = append(returnedHosts, ret) + return nil + }, + ) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + } + + // Now, check set membership changes + setMembershipsToAdd, setMembershipsToRemove, allSetIds := getSetChanges(currentHostMap, newHostMap) + + // Iterate through the sets and update memberships, one transaction per set + for setId := range allSetIds { + hs, err := NewHostSet(ctx, hc.PublicId) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + hs.PublicId = setId + + _, err = r.writer.DoTx( + ctx, + db.StdRetryCnt, + db.ExpBackoff{}, + func(r db.Reader, w db.Writer) error { + msgs := make([]*oplog.Message, 0) + + ticket, err := w.GetTicket(hs) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket")) + } + + // Perform additions + for _, hostId := range setMembershipsToAdd[hs.PublicId] { + membership, err := NewHostSetMember(ctx, hs.PublicId, hostId) + if err != nil { + return errors.Wrap(ctx, err, op) + } + + var hOplogMsg oplog.Message + if err := w.Create(ctx, membership, db.NewOplogMsg(&hOplogMsg)); err != nil { + return errors.Wrap(ctx, err, op) + } + msgs = append(msgs, &hOplogMsg) + } + + // Perform removals + for _, hostId := range setMembershipsToRemove[hs.PublicId] { + membership, err := NewHostSetMember(ctx, hs.PublicId, hostId) + if err != nil { + return errors.Wrap(ctx, err, op) + } + + var hOplogMsg oplog.Message + rows, err := w.Delete(ctx, membership, db.NewOplogMsg(&hOplogMsg)) + if err != nil { + return errors.Wrap(ctx, err, op) + } + if rows != 1 { + return errors.New(ctx, errors.RecordNotFound, op, "record not found when deleting set membership") + } + msgs = append(msgs, &hOplogMsg) + } + + metadata := hc.oplog(oplog.OpType_OP_TYPE_UPDATE) + if err := w.WriteOplogEntryWith(ctx, oplogWrapper, ticket, metadata, msgs); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog")) + } + + return nil + }, + ) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + } + + return returnedHosts, nil +} diff --git a/internal/host/plugin/job_set_sync_test.go b/internal/host/plugin/job_set_sync_test.go new file mode 100644 index 000000000..66e8705d5 --- /dev/null +++ b/internal/host/plugin/job_set_sync_test.go @@ -0,0 +1,483 @@ +package plugin + +import ( + "context" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/db/timestamp" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + hostplg "github.com/hashicorp/boundary/internal/plugin/host" + "github.com/hashicorp/boundary/internal/scheduler" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" + assertpkg "github.com/stretchr/testify/assert" + requirepkg "github.com/stretchr/testify/require" +) + +func TestNewSetSyncJob(t *testing.T) { + t.Parallel() + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + + plg := hostplg.TestPlugin(t, conn, "lookup") + plgm := map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): NewWrappingPluginClient(&TestPluginServer{}), + } + + type args struct { + r db.Reader + w db.Writer + kms *kms.Kms + plgm map[string]plgpb.HostPluginServiceClient + } + tests := []struct { + name string + args args + options []Option + wantLimit int + wantErr bool + wantErrCode errors.Code + }{ + { + name: "nil reader", + args: args{ + w: rw, + kms: kmsCache, + plgm: plgm, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "nil writer", + args: args{ + r: rw, + kms: kmsCache, + plgm: plgm, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "nil kms", + args: args{ + r: rw, + w: rw, + plgm: plgm, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "nil plgm", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "valid-no-options", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + plgm: plgm, + }, + wantLimit: db.DefaultLimit, + }, + { + name: "valid-with-limit", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + plgm: plgm, + }, + options: []Option{WithLimit(100)}, + wantLimit: 100, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assertpkg.New(t), requirepkg.New(t) + + got, err := newSetSyncJob(ctx, tt.args.r, tt.args.w, tt.args.kms, tt.args.plgm, tt.options...) + if tt.wantErr { + require.Error(err) + assert.Nil(got) + assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "Unexpected error %s", err) + return + } + require.NoError(err) + require.NotNil(got) + assert.Equal(tt.args.r, got.reader) + assert.Equal(tt.args.w, got.writer) + assert.Equal(tt.args.kms, got.kms) + assert.Equal(tt.wantLimit, got.limit) + }) + } +} + +func TestSetSyncJob_Run(t *testing.T) { + t.Parallel() + assert, require := assertpkg.New(t), requirepkg.New(t) + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + sched := scheduler.TestScheduler(t, conn, wrapper) + + plgServer := &TestPluginServer{} + plg := hostplg.TestPlugin(t, conn, "run") + plgm := map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): NewWrappingPluginClient(plgServer), + } + + r, err := newSetSyncJob(ctx, rw, rw, kmsCache, plgm) + require.NoError(err) + sche := scheduler.TestScheduler(t, conn, wrapper) + err = sche.RegisterJob(context.Background(), r) + require.NoError(err) + + err = r.Run(context.Background()) + require.NoError(err) + // No sets should have been synced. + assert.Equal(0, r.numProcessed) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + + cat := TestCatalog(t, conn, prj.GetPublicId(), plg.GetPublicId()) + set1 := TestSet(t, conn, kmsCache, sched, cat, plgm) + plgServer.ListHostsFn = func(ctx context.Context, req *plgpb.ListHostsRequest) (*plgpb.ListHostsResponse, error) { + assert.Len(req.GetSets(), 1) + assert.Equal(set1.GetPublicId(), req.GetSets()[0].GetId()) + return &plgpb.ListHostsResponse{ + Hosts: []*plgpb.ListHostsResponseHost{ + { + ExternalId: "first", + IpAddresses: []string{"10.0.0.1"}, + SetIds: []string{req.GetSets()[0].GetId()}, + }, + }, + }, nil + } + + hsa := &hostSetAgg{PublicId: set1.GetPublicId()} + require.NoError(rw.LookupByPublicId(ctx, hsa)) + assert.Less(hsa.LastSyncTime.AsTime().UnixNano(), hsa.CreateTime.AsTime().UnixNano()) + + // Run sync again with the newly created set + err = r.Run(context.Background()) + require.NoError(err) + // The single existing set should have been processed + assert.Equal(1, r.numSets) + assert.Equal(1, r.numProcessed) + + require.NoError(rw.LookupByPublicId(ctx, hsa)) + assert.Greater(hsa.LastSyncTime.AsTime().UnixNano(), hsa.CreateTime.AsTime().UnixNano()) + assert.False(hsa.NeedSync) + firstSyncTime := hsa.LastSyncTime + + // Run sync again with the freshly synced set + err = r.Run(context.Background()) + require.NoError(err) + // The single existing set should have been processed + assert.Equal(0, r.numSets) + assert.Equal(0, r.numProcessed) + + // Set needs update + hs, err := hsa.toHostSet(ctx) + require.NoError(err) + hs.NeedSync = true + count, err := rw.Update(ctx, hs, []string{"NeedSync"}, nil) + require.NoError(err) + assert.Equal(1, count) + assert.True(hs.NeedSync) + + // Run sync again with the set needing update + err = r.Run(context.Background()) + require.NoError(err) + // The single existing set should have been processed + assert.Equal(1, r.numSets) + assert.Equal(1, r.numProcessed) + + require.NoError(rw.LookupByPublicId(ctx, hs)) + assert.Greater(hs.GetLastSyncTime().AsTime().UnixNano(), firstSyncTime.AsTime().UnixNano()) + assert.False(hs.GetNeedSync()) + + // Now, run a battery of tests with values for SyncIntervalSeconds + type setArgs struct { + syncIntervalSeconds int32 + lastSyncTime *timestamp.Timestamp + needsSync bool + } + tests := []struct { + name string + setArgs setArgs + expectSync bool + }{ + { + name: "never-synced-before-needs-sync-false", + setArgs: setArgs{ + lastSyncTime: timestamp.New(time.Unix(0, 0)), + needsSync: false, + }, + expectSync: true, + }, + { + name: "never-synced-before-needs-sync-true", + setArgs: setArgs{ + lastSyncTime: timestamp.New(time.Unix(0, 0)), + needsSync: true, + }, + expectSync: true, + }, + { + name: "never-synced-before-sync-disabled", + setArgs: setArgs{ + syncIntervalSeconds: -1, + lastSyncTime: timestamp.New(time.Unix(0, 0)), + needsSync: true, + }, + expectSync: true, + }, + { + name: "synced-just-now", + setArgs: setArgs{ + lastSyncTime: timestamp.Now(), + needsSync: false, + }, + expectSync: false, + }, + { + name: "synced-just-now-need-sync", + setArgs: setArgs{ + lastSyncTime: timestamp.Now(), + needsSync: true, + }, + expectSync: true, + }, + { + name: "synced-just-now-need-sync-but-sync-disabled", + setArgs: setArgs{ + syncIntervalSeconds: -1, + lastSyncTime: timestamp.Now(), + needsSync: true, + }, + expectSync: true, + }, + { + name: "synced-30-seconds-ago-default-time", + setArgs: setArgs{ + lastSyncTime: timestamp.New(time.Now().Add(-60 * time.Second)), + needsSync: false, + }, + expectSync: false, + }, + { + name: "synced-30-seconds-ago-custom-time", + setArgs: setArgs{ + syncIntervalSeconds: 5, + lastSyncTime: timestamp.New(time.Now().Add(-60 * time.Second)), + needsSync: false, + }, + expectSync: true, + }, + { + name: "synced-30-seconds-ago-custom-larger-time", + setArgs: setArgs{ + syncIntervalSeconds: 90, + lastSyncTime: timestamp.New(time.Now().Add(-60 * time.Second)), + needsSync: false, + }, + expectSync: false, + }, + { + name: "synced-30-seconds-ago-custom-larger-time-need-sync", + setArgs: setArgs{ + syncIntervalSeconds: 60, + lastSyncTime: timestamp.New(time.Now().Add(-60 * time.Second)), + needsSync: true, + }, + expectSync: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assertpkg.New(t), requirepkg.New(t) + + // Update set + hs.LastSyncTime = tt.setArgs.lastSyncTime + hs.NeedSync = tt.setArgs.needsSync + hs.SyncIntervalSeconds = tt.setArgs.syncIntervalSeconds + fieldMaskPaths := []string{"LastSyncTime", "NeedSync"} + var setToNullPaths []string + if hs.SyncIntervalSeconds == 0 { + setToNullPaths = []string{"SyncIntervalSeconds"} + } else { + fieldMaskPaths = append(fieldMaskPaths, "SyncIntervalSeconds") + } + count, err := rw.Update(ctx, hs, fieldMaskPaths, setToNullPaths) + require.NoError(err) + assert.Equal(1, count) + + // Run job + err = r.Run(context.Background()) + require.NoError(err) + + // Validate results + var expNum int + if tt.expectSync { + expNum = 1 + } + assert.Equal(expNum, r.numSets) + assert.Equal(expNum, r.numProcessed) + }) + } +} + +func TestSetSyncJob_NextRunIn(t *testing.T) { + t.Parallel() + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + sched := scheduler.TestScheduler(t, conn, wrapper) + kmsCache := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + _, prj := iam.TestScopes(t, iamRepo) + plg := hostplg.TestPlugin(t, conn, "lookup") + plgm := map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): NewWrappingPluginClient(&TestPluginServer{}), + } + catalog := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + hostSet := TestSet(t, conn, kmsCache, sched, catalog, plgm) + + type setArgs struct { + syncIntervalSeconds int32 + lastSyncTime *timestamp.Timestamp + needsSync bool + } + tests := []struct { + name string + syncInterval time.Duration + setArgs setArgs + want time.Duration + }{ + { + name: "never-synced-before", + setArgs: setArgs{ + lastSyncTime: timestamp.New(time.Unix(0, 0)), + needsSync: false, + }, + want: 0, + }, + { + name: "never-synced-before-with-sync-interval", + setArgs: setArgs{ + syncIntervalSeconds: 60, + lastSyncTime: timestamp.New(time.Unix(0, 0)), + needsSync: false, + }, + want: 0, + }, + { + name: "synced-just-now", + setArgs: setArgs{ + lastSyncTime: timestamp.Now(), + needsSync: false, + }, + want: setSyncJobRunInterval, + }, + { + name: "synced-just-now-with-sync-interval", + setArgs: setArgs{ + syncIntervalSeconds: 180, + lastSyncTime: timestamp.Now(), + needsSync: false, + }, + want: 3 * time.Minute, + }, + { + name: "synced-just-now-need-sync", + setArgs: setArgs{ + lastSyncTime: timestamp.Now(), + needsSync: true, + }, + want: 0, + }, + { + name: "synced-just-now-need-sync-with-sync-interval", + setArgs: setArgs{ + syncIntervalSeconds: 60, + lastSyncTime: timestamp.Now(), + needsSync: true, + }, + want: 0, + }, + { + name: "synced-a-bit-ago", + setArgs: setArgs{ + lastSyncTime: timestamp.New(time.Now().Add(-4 * time.Minute)), + needsSync: false, + }, + want: time.Until(time.Now().Add(setSyncJobRunInterval - (4 * time.Minute))), + }, + { + name: "synced-a-bit-ago-with-sync-interval", + setArgs: setArgs{ + syncIntervalSeconds: 300, + lastSyncTime: timestamp.New(time.Now().Add(-4 * time.Minute)), + needsSync: false, + }, + want: time.Minute, + }, + { + name: "automatic-sync-disabled", + setArgs: setArgs{ + syncIntervalSeconds: -1, + lastSyncTime: timestamp.New(time.Now().Add(-4 * time.Minute)), + needsSync: false, + }, + want: setSyncJobRunInterval, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assertpkg.New(t), requirepkg.New(t) + r, err := newSetSyncJob(ctx, rw, rw, kmsCache, plgm) + assert.NoError(err) + require.NotNil(r) + + hostSet.NeedSync = tt.setArgs.needsSync + hostSet.LastSyncTime = tt.setArgs.lastSyncTime + hostSet.SyncIntervalSeconds = tt.setArgs.syncIntervalSeconds + fieldMaskPaths := []string{"LastSyncTime", "NeedSync"} + var setToNullPaths []string + if hostSet.SyncIntervalSeconds == 0 { + setToNullPaths = []string{"SyncIntervalSeconds"} + } else { + fieldMaskPaths = append(fieldMaskPaths, "SyncIntervalSeconds") + } + _, err = rw.Update(ctx, hostSet, fieldMaskPaths, setToNullPaths) + require.NoError(err) + + got, err := r.NextRunIn() + require.NoError(err) + // Round to five seconds to account for lost time between updating set and determining next run + assert.Equal(tt.want.Round(5*time.Second), got.Round(5*time.Second)) + }) + } +} diff --git a/internal/host/plugin/jobs.go b/internal/host/plugin/jobs.go new file mode 100644 index 000000000..3666d694d --- /dev/null +++ b/internal/host/plugin/jobs.go @@ -0,0 +1,32 @@ +package plugin + +import ( + "context" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/scheduler" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" +) + +// RegisterJobs registers plugin host related jobs with the provided scheduler. +func RegisterJobs(ctx context.Context, scheduler *scheduler.Scheduler, r db.Reader, w db.Writer, kms *kms.Kms, plgm map[string]plgpb.HostPluginServiceClient) error { + const op = "plugin.RegisterJobs" + setSyncJob, err := newSetSyncJob(ctx, r, w, kms, plgm) + if err != nil { + return errors.Wrap(ctx, err, op) + } + if err = scheduler.RegisterJob(ctx, setSyncJob); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("set sync job")) + } + orphanedHostCleanupJob, err := newOrphanedHostCleanupJob(ctx, r, w, kms) + if err != nil { + return errors.Wrap(ctx, err, op) + } + if err = scheduler.RegisterJob(ctx, orphanedHostCleanupJob); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("orphaned host cleanup job")) + } + + return nil +} diff --git a/internal/host/plugin/loopback.go b/internal/host/plugin/loopback.go new file mode 100644 index 000000000..34f32a69a --- /dev/null +++ b/internal/host/plugin/loopback.go @@ -0,0 +1,150 @@ +package plugin + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/errors" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" + "github.com/mitchellh/mapstructure" +) + +const loopbackPluginHostInfoAttrField = "host_info" + +var _ plgpb.HostPluginServiceServer = (*loopbackPlugin)(nil) + +// loopbackPlugin provides a host plugin with functionality useful for certain +// kinds of testing. It is not (currently) thread-safe. +// +// Over time, if useful, it can be enhanced to do things like handle multiple +// hosts per set. +type loopbackPlugin struct { + *TestPluginServer + + hostMap map[string][]*loopbackPluginHostInfo +} + +type loopbackPluginHostInfo struct { + ExternalId string `mapstructure:"external_id"` + IpAddresses []string `mapstructure:"ip_addresses"` + DnsNames []string `mapstructure:"dns_names"` +} + +// NewLoopbackPlugin returns a new loopback plugin +func NewLoopbackPlugin() plgpb.HostPluginServiceServer { + ret := &loopbackPlugin{ + TestPluginServer: new(TestPluginServer), + hostMap: make(map[string][]*loopbackPluginHostInfo), + } + ret.OnCreateCatalogFn = ret.onCreateCatalog + ret.OnCreateSetFn = ret.onCreateSet + ret.OnUpdateCatalogFn = ret.onUpdateCatalog + ret.OnDeleteSetFn = ret.onDeleteSet + ret.ListHostsFn = ret.listHosts + return ret +} + +func (l *loopbackPlugin) onCreateCatalog(ctx context.Context, req *plgpb.OnCreateCatalogRequest) (*plgpb.OnCreateCatalogResponse, error) { + const op = "plugin.(loopbackPlugin).onCreateCatalog" + if req == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "req is nil") + } + if cat := req.GetCatalog(); cat != nil { + if secrets := cat.GetSecrets(); secrets != nil { + return &plgpb.OnCreateCatalogResponse{ + Persisted: &plgpb.HostCatalogPersisted{ + Secrets: secrets, + }, + }, nil + } + } + return &plgpb.OnCreateCatalogResponse{}, nil +} + +func (l *loopbackPlugin) onCreateSet(ctx context.Context, req *plgpb.OnCreateSetRequest) (*plgpb.OnCreateSetResponse, error) { + const op = "plugin.(loopbackPlugin).onCreateSet" + if req == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "req is nil") + } + set := req.GetSet() + if set == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "set is nil") + } + if attrs := set.GetAttributes(); attrs != nil { + attrsMap := attrs.AsMap() + if field := attrsMap[loopbackPluginHostInfoAttrField]; field != nil { + switch t := field.(type) { + case []interface{}: + for _, h := range t { + hostInfo := new(loopbackPluginHostInfo) + if err := mapstructure.Decode(h, hostInfo); err != nil { + return nil, errors.Wrap(ctx, err, op) + } + l.hostMap[set.GetId()] = append(l.hostMap[set.GetId()], hostInfo) + } + case map[string]interface{}: + hostInfo := new(loopbackPluginHostInfo) + if err := mapstructure.Decode(t, hostInfo); err != nil { + return nil, errors.Wrap(ctx, err, op) + } + l.hostMap[set.GetId()] = append(l.hostMap[set.GetId()], hostInfo) + default: + return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unknown host info type %T", t)) + } + } + } + return nil, nil +} + +func (l *loopbackPlugin) onUpdateCatalog(ctx context.Context, req *plgpb.OnUpdateCatalogRequest) (*plgpb.OnUpdateCatalogResponse, error) { + const op = "plugin.(loopbackPlugin).onUpdateCatalog" + if req == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "req is nil") + } + if cat := req.GetNewCatalog(); cat != nil { + if secrets := cat.GetSecrets(); secrets != nil { + return &plgpb.OnUpdateCatalogResponse{ + Persisted: &plgpb.HostCatalogPersisted{ + Secrets: secrets, + }, + }, nil + } + } + return &plgpb.OnUpdateCatalogResponse{}, nil +} + +func (l *loopbackPlugin) onDeleteSet(ctx context.Context, req *plgpb.OnDeleteSetRequest) (*plgpb.OnDeleteSetResponse, error) { + const op = "plugin.(loopbackPlugin).onDeleteSet" + if req == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "req is nil") + } + set := req.GetSet() + if set == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "set is nil") + } + delete(l.hostMap, set.GetId()) + return nil, nil +} + +func (l *loopbackPlugin) listHosts(ctx context.Context, req *plgpb.ListHostsRequest) (*plgpb.ListHostsResponse, error) { + const op = "plugin.(loopbackPlugin).listHosts" + if req == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "req is nil") + } + resp := new(plgpb.ListHostsResponse) + for _, set := range req.GetSets() { + hostInfos := l.hostMap[set.GetId()] + if len(hostInfos) == 0 { + continue + } + for _, host := range hostInfos { + resp.Hosts = append(resp.Hosts, &plgpb.ListHostsResponseHost{ + SetIds: []string{set.GetId()}, + ExternalId: host.ExternalId, + IpAddresses: host.IpAddresses, + DnsNames: host.DnsNames, + }) + } + } + return resp, nil +} diff --git a/internal/host/plugin/loopback_test.go b/internal/host/plugin/loopback_test.go new file mode 100644 index 000000000..691a79736 --- /dev/null +++ b/internal/host/plugin/loopback_test.go @@ -0,0 +1,362 @@ +package plugin + +import ( + "context" + "testing" + + "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs" + "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" + ta "github.com/stretchr/testify/assert" + tr "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/structpb" +) + +// TestLoopbackPlugin is a quick test of basic functionality. +func TestLoopbackPlugin(t *testing.T) { + require, assert := tr.New(t), ta.New(t) + ctx := context.Background() + + plg := NewLoopbackPlugin() + secretsMap := map[string]interface{}{ + "key1": "key2", + "baz": true, + } + secrets, err := structpb.NewStruct(secretsMap) + require.NoError(err) + + // First, test that if we give it secrets, those secrets come back as + // persisted data + catResp, err := plg.OnCreateCatalog(ctx, &plgpb.OnCreateCatalogRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Secrets: secrets, + }, + }) + require.NoError(err) + require.NotNil(catResp) + require.NotNil(catResp.GetPersisted()) + require.NotNil(catResp.GetPersisted().GetSecrets()) + assert.EqualValues(secretsMap, catResp.GetPersisted().GetSecrets().AsMap()) + + newSecretsMap := map[string]interface{}{ + "key1": "key2", + "baz": true, + } + newSecrets, err := structpb.NewStruct(newSecretsMap) + require.NoError(err) + + // First, test that if we give it secrets, those secrets come back as + // persisted data + upResp, err := plg.OnUpdateCatalog(ctx, &plgpb.OnUpdateCatalogRequest{ + CurrentCatalog: &hostcatalogs.HostCatalog{}, + NewCatalog: &hostcatalogs.HostCatalog{ + Secrets: newSecrets, + }, + Persisted: &plgpb.HostCatalogPersisted{ + Secrets: secrets, + }, + }) + require.NoError(err) + require.NotNil(upResp) + require.NotNil(upResp.GetPersisted()) + require.NotNil(upResp.GetPersisted().GetSecrets()) + assert.EqualValues(newSecretsMap, upResp.GetPersisted().GetSecrets().AsMap()) + + // Add data to some sets + hostInfo1 := map[string]interface{}{ + loopbackPluginHostInfoAttrField: map[string]interface{}{ + "set_ids": []interface{}{"set1"}, + "external_id": "host1", + "ip_addresses": []interface{}{"1.2.3.4", "2.3.4.5"}, + "dns_names": []interface{}{"foo.com"}, + }, + } + attrs, err := structpb.NewStruct(hostInfo1) + require.NoError(err) + _, err = plg.OnCreateSet(ctx, &plgpb.OnCreateSetRequest{ + Set: &hostsets.HostSet{ + Id: "set1", + Attributes: attrs, + }, + }) + require.NoError(err) + hostInfo2 := map[string]interface{}{ + loopbackPluginHostInfoAttrField: map[string]interface{}{ + "set_ids": []interface{}{"set2"}, + "external_id": "host2", + "ip_addresses": []interface{}{"5.6.7.8", "6.7.8.9"}, + "dns_names": []interface{}{"bar.com"}, + }, + } + attrs, err = structpb.NewStruct(hostInfo2) + require.NoError(err) + _, err = plg.OnCreateSet(ctx, &plgpb.OnCreateSetRequest{ + Set: &hostsets.HostSet{ + Id: "set2", + Attributes: attrs, + }, + }) + require.NoError(err) + + // Define test struct and validation function + type testInfo struct { + name string + sets []string + found []map[string]interface{} + } + validateSets := func(t *testing.T, tt testInfo) { + require, assert := tr.New(t), ta.New(t) + var hostSets []*hostsets.HostSet + for _, set := range tt.sets { + hostSets = append(hostSets, &hostsets.HostSet{Id: set}) + } + resp, err := plg.ListHosts(ctx, &plgpb.ListHostsRequest{ + Sets: hostSets, + }) + require.NoError(err) + if len(tt.found) == 0 { + assert.Len(resp.GetHosts(), 0) + return + } + + require.Greater(len(resp.GetHosts()), 0) + + var found []map[string]interface{} + for _, host := range resp.GetHosts() { + hostMap := map[string]interface{}{ + "external_id": host.GetExternalId(), + } + var sets []interface{} + for _, set := range host.SetIds { + sets = append(sets, set) + } + var ips []interface{} + for _, ip := range host.GetIpAddresses() { + ips = append(ips, ip) + } + var names []interface{} + for _, name := range host.GetDnsNames() { + names = append(names, name) + } + if len(sets) > 0 { + hostMap["set_ids"] = sets + } + if len(ips) > 0 { + hostMap["ip_addresses"] = ips + } + if len(names) > 0 { + hostMap["dns_names"] = names + } + found = append(found, hostMap) + } + assert.ElementsMatch(tt.found, found) + } + + // First set of tests: check that we can look up sets individually and + // together + setTests := []testInfo{ + { + name: "set 1", + sets: []string{"set1"}, + found: []map[string]interface{}{ + hostInfo1[loopbackPluginHostInfoAttrField].(map[string]interface{}), + }, + }, + { + name: "set 2", + sets: []string{"set2"}, + found: []map[string]interface{}{ + hostInfo2[loopbackPluginHostInfoAttrField].(map[string]interface{}), + }, + }, + { + name: "sets 1 and 2", + sets: []string{"set1", "set2"}, + found: []map[string]interface{}{ + hostInfo1[loopbackPluginHostInfoAttrField].(map[string]interface{}), + hostInfo2[loopbackPluginHostInfoAttrField].(map[string]interface{}), + }, + }, + } + for _, tt := range setTests { + t.Run(tt.name, func(t *testing.T) { + validateSets(t, tt) + }) + } + + // Remove a set + _, err = plg.OnDeleteSet(ctx, &plgpb.OnDeleteSetRequest{ + Set: &hostsets.HostSet{ + Id: "set1", + }, + }) + require.NoError(err) + + // Run tests again, making sure we no longer find that host either + // individually or together + setTests = []testInfo{ + { + name: "set 1 deleted", + sets: []string{"set1"}, + found: []map[string]interface{}{}, + }, + { + name: "set 2 not deleted", + sets: []string{"set2"}, + found: []map[string]interface{}{ + hostInfo2[loopbackPluginHostInfoAttrField].(map[string]interface{}), + }, + }, + { + name: "sets 1 and 2 set 1 deleted", + sets: []string{"set1", "set2"}, + found: []map[string]interface{}{ + hostInfo2[loopbackPluginHostInfoAttrField].(map[string]interface{}), + }, + }, + } + for _, tt := range setTests { + t.Run(tt.name, func(t *testing.T) { + validateSets(t, tt) + }) + } +} + +func TestLoopbackPluginArrays(t *testing.T) { + require := tr.New(t) + ctx := context.Background() + + plg := NewLoopbackPlugin() + + // Add data to some sets + hostInfo1 := map[string]interface{}{ + loopbackPluginHostInfoAttrField: []interface{}{ + map[string]interface{}{ + "set_ids": []interface{}{"set1"}, + "external_id": "host1a", + "ip_addresses": []interface{}{"1.2.3.4", "2.3.4.5"}, + "dns_names": []interface{}{"foo.com"}, + }, + map[string]interface{}{ + "set_ids": []interface{}{"set1"}, + "external_id": "host1b", + "ip_addresses": []interface{}{"3.4.5.6", "4.5.6.7"}, + "dns_names": []interface{}{"bar.com"}, + }, + }, + } + attrs, err := structpb.NewStruct(hostInfo1) + require.NoError(err) + _, err = plg.OnCreateSet(ctx, &plgpb.OnCreateSetRequest{ + Set: &hostsets.HostSet{ + Id: "set1", + Attributes: attrs, + }, + }) + require.NoError(err) + hostInfo2 := map[string]interface{}{ + loopbackPluginHostInfoAttrField: []interface{}{ + map[string]interface{}{ + "set_ids": []interface{}{"set2"}, + "external_id": "host2a", + "ip_addresses": []interface{}{"10.20.30.40", "20.30.40.50"}, + "dns_names": []interface{}{"foz.com"}, + }, + map[string]interface{}{ + "set_ids": []interface{}{"set2"}, + "external_id": "host2b", + "ip_addresses": []interface{}{"30.40.50.60", "40.50.60.70"}, + "dns_names": []interface{}{"baz.com"}, + }, + }, + } + attrs, err = structpb.NewStruct(hostInfo2) + require.NoError(err) + _, err = plg.OnCreateSet(ctx, &plgpb.OnCreateSetRequest{ + Set: &hostsets.HostSet{ + Id: "set2", + Attributes: attrs, + }, + }) + require.NoError(err) + + // Define test struct and validation function + type testInfo struct { + name string + sets []string + found []interface{} + } + validateSets := func(t *testing.T, tt testInfo) { + require, assert := tr.New(t), ta.New(t) + var hostSets []*hostsets.HostSet + for _, set := range tt.sets { + hostSets = append(hostSets, &hostsets.HostSet{Id: set}) + } + resp, err := plg.ListHosts(ctx, &plgpb.ListHostsRequest{ + Sets: hostSets, + }) + require.NoError(err) + if len(tt.found) == 0 { + assert.Len(resp.GetHosts(), 0) + return + } + + require.Greater(len(resp.GetHosts()), 0) + + var found []interface{} + for _, host := range resp.GetHosts() { + hostMap := map[string]interface{}{ + "external_id": host.GetExternalId(), + } + var sets []interface{} + for _, set := range host.SetIds { + sets = append(sets, set) + } + var ips []interface{} + for _, ip := range host.GetIpAddresses() { + ips = append(ips, ip) + } + var names []interface{} + for _, name := range host.GetDnsNames() { + names = append(names, name) + } + if len(sets) > 0 { + hostMap["set_ids"] = sets + } + if len(ips) > 0 { + hostMap["ip_addresses"] = ips + } + if len(names) > 0 { + hostMap["dns_names"] = names + } + found = append(found, hostMap) + } + assert.ElementsMatch(tt.found, found) + } + + // First set of tests: check that we can look up sets individually and + // together + setTests := []testInfo{ + { + name: "set 1", + sets: []string{"set1"}, + found: hostInfo1[loopbackPluginHostInfoAttrField].([]interface{}), + }, + { + name: "set 2", + sets: []string{"set2"}, + found: hostInfo2[loopbackPluginHostInfoAttrField].([]interface{}), + }, + { + name: "sets 1 and 2", + sets: []string{"set1", "set2"}, + found: append(hostInfo1[loopbackPluginHostInfoAttrField].([]interface{}), + hostInfo2[loopbackPluginHostInfoAttrField].([]interface{})...), + }, + } + for _, tt := range setTests { + t.Run(tt.name, func(t *testing.T) { + validateSets(t, tt) + }) + } +} diff --git a/internal/host/plugin/options.go b/internal/host/plugin/options.go new file mode 100644 index 000000000..6536cebcf --- /dev/null +++ b/internal/host/plugin/options.go @@ -0,0 +1,124 @@ +package plugin + +import "google.golang.org/protobuf/types/known/structpb" + +// getOpts - iterate the inbound Options and return a struct +func getOpts(opt ...Option) options { + opts := getDefaultOptions() + for _, o := range opt { + o(&opts) + } + return opts +} + +// Option - how Options are passed as arguments. +type Option func(*options) + +// options = how options are represented +type options struct { + withPublicId string + withPluginId string + withName string + withDescription string + withAttributes *structpb.Struct + withSecrets *structpb.Struct + withPreferredEndpoints []string + withSyncIntervalSeconds int32 + withIpAddresses []string + withDnsNames []string + withLimit int + withSetIds []string +} + +func getDefaultOptions() options { + return options{ + withAttributes: &structpb.Struct{}, + } +} + +// WithPluginId provides an optional plugin id. +func withPluginId(with string) Option { + return func(o *options) { + o.withPluginId = with + } +} + +// WithPublicId provides an optional public id. +func WithPublicId(with string) Option { + return func(o *options) { + o.withPublicId = with + } +} + +// WithDescription provides an optional description. +func WithDescription(desc string) Option { + return func(o *options) { + o.withDescription = desc + } +} + +// WithName provides an optional name. +func WithName(name string) Option { + return func(o *options) { + o.withName = name + } +} + +// WithAttributes provides an optional attributes field. +func WithAttributes(attrs *structpb.Struct) Option { + return func(o *options) { + o.withAttributes = attrs + } +} + +// WithSecrets provides an optional secrets field. +func WithSecrets(secrets *structpb.Struct) Option { + return func(o *options) { + o.withSecrets = secrets + } +} + +// WithPreferredEndpoints provides an optional preferred endpoints field. +func WithPreferredEndpoints(with []string) Option { + return func(o *options) { + o.withPreferredEndpoints = with + } +} + +// WithSyncIntervalSeconds provides an optional sync interval, in seconds +func WithSyncIntervalSeconds(with int32) Option { + return func(o *options) { + o.withSyncIntervalSeconds = with + } +} + +// withIpAddresses provides an optional list of ip addresses. +func withIpAddresses(with []string) Option { + return func(o *options) { + o.withIpAddresses = with + } +} + +// withDnsNames provides an optional list of dns names. +func withDnsNames(with []string) Option { + return func(o *options) { + o.withDnsNames = with + } +} + +// WithLimit provides an option to provide a limit. Intentionally allowing +// negative integers. If WithLimit < 0, then unlimited results are +// returned. If WithLimit == 0, then default limits are used for results. +func WithLimit(l int) Option { + return func(o *options) { + o.withLimit = l + } +} + +// WithSetIds provides the ability to restrict lookups to particular matching +// sets. +func WithSetIds(with []string) Option { + return func(o *options) { + o.withSetIds = with + } +} diff --git a/internal/host/plugin/options_test.go b/internal/host/plugin/options_test.go new file mode 100644 index 000000000..e55321cf1 --- /dev/null +++ b/internal/host/plugin/options_test.go @@ -0,0 +1,71 @@ +package plugin + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_GetOpts(t *testing.T) { + t.Parallel() + t.Run("WithPublicId", func(t *testing.T) { + opts := getOpts(WithPublicId("test")) + testOpts := getDefaultOptions() + testOpts.withPublicId = "test" + assert.Equal(t, opts, testOpts) + }) + t.Run("WithName", func(t *testing.T) { + opts := getOpts(WithName("test")) + testOpts := getDefaultOptions() + testOpts.withName = "test" + assert.Equal(t, opts, testOpts) + }) + t.Run("WithSyncIntervalSeconds", func(t *testing.T) { + opts := getOpts(WithSyncIntervalSeconds(5)) + testOpts := getDefaultOptions() + testOpts.withSyncIntervalSeconds = 5 + assert.Equal(t, opts, testOpts) + }) + t.Run("WithPluginId", func(t *testing.T) { + opts := getOpts(withPluginId("test")) + testOpts := getDefaultOptions() + testOpts.withPluginId = "test" + assert.Equal(t, opts, testOpts) + }) + t.Run("WithDescription", func(t *testing.T) { + opts := getOpts(WithDescription("test desc")) + testOpts := getDefaultOptions() + testOpts.withDescription = "test desc" + assert.Equal(t, opts, testOpts) + }) + t.Run("WithLimit", func(t *testing.T) { + opts := getOpts(WithLimit(5)) + testOpts := getDefaultOptions() + testOpts.withLimit = 5 + assert.Equal(t, opts, testOpts) + }) + t.Run("WithPreferredEndpoints", func(t *testing.T) { + opts := getOpts(WithPreferredEndpoints([]string{"foo"})) + testOpts := getDefaultOptions() + testOpts.withPreferredEndpoints = []string{"foo"} + assert.EqualValues(t, opts, testOpts) + }) + t.Run("withDnsNames", func(t *testing.T) { + opts := getOpts(withDnsNames([]string{"foo"})) + testOpts := getDefaultOptions() + testOpts.withDnsNames = []string{"foo"} + assert.EqualValues(t, opts, testOpts) + }) + t.Run("withIpAddresses", func(t *testing.T) { + opts := getOpts(withIpAddresses([]string{"foo"})) + testOpts := getDefaultOptions() + testOpts.withIpAddresses = []string{"foo"} + assert.EqualValues(t, opts, testOpts) + }) + t.Run("withSetIds", func(t *testing.T) { + opts := getOpts(WithSetIds([]string{"foo"})) + testOpts := getDefaultOptions() + testOpts.withSetIds = []string{"foo"} + assert.EqualValues(t, opts, testOpts) + }) +} diff --git a/internal/host/plugin/query.go b/internal/host/plugin/query.go new file mode 100644 index 000000000..700a126cc --- /dev/null +++ b/internal/host/plugin/query.go @@ -0,0 +1,46 @@ +package plugin + +const ( + upsertHostCatalogSecretQuery = ` +insert into host_plugin_catalog_secret + (catalog_id, secret, key_id) +values + (@catalog_id, @secret, @key_id) +on conflict (catalog_id) do update + set secret = excluded.secret, + key_id = excluded.key_id +returning *; +` + + deleteHostCatalogSecretQuery = ` +delete from host_plugin_catalog_secret + where catalog_id = @catalog_id; +` + + setSyncNextRunInQuery = ` +select + need_sync as sync_now, + sync_interval_seconds, + case + when sync_interval_seconds is null + then + extract(epoch from (least(now(), last_sync_time) + ?) - now())::int + when sync_interval_seconds > 0 + then + extract(epoch from (least(now(), last_sync_time)) - now())::int + sync_interval_seconds + else + 0 + end resync_in +from host_plugin_set +order by need_sync desc, last_sync_time desc +limit 1; +` + + setSyncJobQuery = ` +need_sync + or +sync_interval_seconds is null and last_sync_time <= wt_add_seconds_to_now(?) + or +sync_interval_seconds > 0 and wt_add_seconds(sync_interval_seconds, last_sync_time) <= current_timestamp +` +) diff --git a/internal/host/plugin/repository.go b/internal/host/plugin/repository.go new file mode 100644 index 000000000..7c0f4d885 --- /dev/null +++ b/internal/host/plugin/repository.go @@ -0,0 +1,71 @@ +// Package plugin provides a plugin host catalog, and plugin host set resource +// which are used to interact with a host plugin as well as a repository to +// perform CRUDL and custom actions on these resources. +package plugin + +import ( + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/host" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/scheduler" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" +) + +// A Repository stores and retrieves the persistent types in the plugin +// package. It is not safe to use a repository concurrently. +type Repository struct { + reader db.Reader + writer db.Writer + kms *kms.Kms + scheduler *scheduler.Scheduler + + // plugins is a map from plugin resource id to host plugin client. + plugins map[string]plgpb.HostPluginServiceClient + // defaultLimit provides a default for limiting the number of results + // returned from the repo + defaultLimit int +} + +// NewRepository creates a new Repository. The returned repository should +// only be used for one transaction and it is not safe for concurrent go +// routines to access it. WithLimit option is used as a repo wide default +// limit applied to all ListX methods. +func NewRepository(r db.Reader, w db.Writer, kms *kms.Kms, sched *scheduler.Scheduler, plgm map[string]plgpb.HostPluginServiceClient, opt ...host.Option) (*Repository, error) { + const op = "plugin.NewRepository" + switch { + case r == nil: + return nil, errors.NewDeprecated(errors.InvalidParameter, op, "db.Reader") + case w == nil: + return nil, errors.NewDeprecated(errors.InvalidParameter, op, "db.Writer") + case kms == nil: + return nil, errors.NewDeprecated(errors.InvalidParameter, op, "kms") + case sched == nil: + return nil, errors.NewDeprecated(errors.InvalidParameter, op, "scheduler") + case plgm == nil: + return nil, errors.NewDeprecated(errors.InvalidParameter, op, "plgm") + } + + opts, err := host.GetOpts(opt...) + if err != nil { + return nil, errors.WrapDeprecated(err, op) + } + if opts.WithLimit == 0 { + // zero signals the boundary defaults should be used. + opts.WithLimit = db.DefaultLimit + } + + plgs := make(map[string]plgpb.HostPluginServiceClient, len(plgm)) + for k, v := range plgm { + plgs[k] = v + } + + return &Repository{ + reader: r, + writer: w, + kms: kms, + scheduler: sched, + plugins: plgs, + defaultLimit: opts.WithLimit, + }, nil +} diff --git a/internal/host/plugin/repository_host.go b/internal/host/plugin/repository_host.go new file mode 100644 index 000000000..5a01cdf10 --- /dev/null +++ b/internal/host/plugin/repository_host.go @@ -0,0 +1,118 @@ +package plugin + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + hostplugin "github.com/hashicorp/boundary/internal/plugin/host" +) + +// LookupHost will look up a host in the repository. If the host is not +// found, it will return nil, nil. All options are ignored. +func (r *Repository) LookupHost(ctx context.Context, publicId string, opt ...Option) (*Host, *hostplugin.Plugin, error) { + const op = "plugin.(Repository).LookupHost" + if publicId == "" { + return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "no public id") + } + ha := &hostAgg{ + PublicId: publicId, + } + if err := r.reader.LookupByPublicId(ctx, ha); err != nil { + if errors.IsNotFoundError(err) { + return nil, nil, nil + } + return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", publicId))) + } + plg, err := r.getPlugin(ctx, ha.PluginId) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + return ha.toHost(), plg, nil +} + +// ListHostsByCatalogId returns a slice of Hosts for the catalogId. +// WithLimit is the only option supported. +func (r *Repository) ListHostsByCatalogId(ctx context.Context, catalogId string, opt ...Option) ([]*Host, *hostplugin.Plugin, error) { + const op = "plugin.(Repository).ListHostsByCatalogId" + if catalogId == "" { + return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "no catalog id") + } + opts := getOpts(opt...) + limit := r.defaultLimit + if opts.withLimit != 0 { + // non-zero signals an override of the default limit for the repo. + limit = opts.withLimit + } + var hostAggs []*hostAgg + err := r.reader.SearchWhere(ctx, &hostAggs, "catalog_id = ?", []interface{}{catalogId}, db.WithLimit(limit)) + + switch { + case err != nil: + return nil, nil, errors.Wrap(ctx, err, op) + case len(hostAggs) == 0: + return nil, nil, nil + } + + pluginId := hostAggs[0].PluginId + plg, err := r.getPlugin(ctx, pluginId) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + + hosts := make([]*Host, 0, len(hostAggs)) + for _, ha := range hostAggs { + hosts = append(hosts, ha.toHost()) + } + + return hosts, plg, nil +} + +// ListHostsBySetId returns a slice of Hosts for the given set IDs. +// WithLimit is the only option supported. +func (r *Repository) ListHostsBySetIds(ctx context.Context, setIds []string, opt ...Option) ([]*Host, error) { + const op = "plugin.(Repository).ListHostsBySetIds" + if len(setIds) == 0 { + return nil, errors.New(ctx, errors.InvalidParameter, op, "no set ids") + } + opts := getOpts(opt...) + limit := r.defaultLimit + if opts.withLimit != 0 { + // non-zero signals an override of the default limit for the repo. + limit = opts.withLimit + } + hs, err := listHostBySetIds(ctx, r.reader, setIds, WithLimit(limit)) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + return hs, nil +} + +func listHostBySetIds(ctx context.Context, reader db.Reader, setIds []string, opt ...Option) ([]*Host, error) { + const op = "plugin.listHostsBySetIds" + opts := getOpts(opt...) + query := ` +public_id in + (select distinct host_id + from host_plugin_set_member + where set_id in (?)) +` + + var hostAggs []*hostAgg + err := reader.SearchWhere(ctx, &hostAggs, query, []interface{}{setIds}, db.WithLimit(opts.withLimit)) + + switch { + case err != nil: + return nil, errors.Wrap(ctx, err, op) + case hostAggs == nil: + return nil, nil + } + + hosts := make([]*Host, 0, len(hostAggs)) + for _, ha := range hostAggs { + hosts = append(hosts, ha.toHost()) + } + + return hosts, nil +} diff --git a/internal/host/plugin/repository_host_catalog.go b/internal/host/plugin/repository_host_catalog.go new file mode 100644 index 000000000..74464774a --- /dev/null +++ b/internal/host/plugin/repository_host_catalog.go @@ -0,0 +1,678 @@ +package plugin + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/host" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/libs/patchstruct" + "github.com/hashicorp/boundary/internal/observability/event" + "github.com/hashicorp/boundary/internal/oplog" + hostplugin "github.com/hashicorp/boundary/internal/plugin/host" + pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs" + pbset "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +// CreateCatalog inserts c into the repository and returns a new +// HostCatalog containing the catalog's PublicId. c must contain a valid +// ScopeID and PluginID. c must not contain a PublicId. The PublicId is +// generated and assigned by this method. opt is ignored. +// +// c.Secret, c.Name and c.Description are optional. If c.Name is set, it must be +// unique within c.ScopeID. If c.Secret is set, it will be stored encrypted but +// not included in the returned *HostCatalog. +// +// Both c.CreateTime and c.UpdateTime are ignored. +func (r *Repository) CreateCatalog(ctx context.Context, c *HostCatalog, _ ...Option) (*HostCatalog, *hostplugin.Plugin, error) { + const op = "plugin.(Repository).CreateCatalog" + if c == nil { + return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "nil HostCatalog") + } + if c.HostCatalog == nil { + return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "nil embedded HostCatalog") + } + if c.ScopeId == "" { + return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "no scope id") + } + if c.PublicId != "" { + return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "public id not empty") + } + if c.PluginId == "" { + return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "no plugin id") + } + if c.Attributes == nil { + return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "nil attributes") + } + c = c.clone() + id, err := newHostCatalogId(ctx) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + c.PublicId = id + + // Use PatchBytes' functionality that does not add keys where the values + // are nil to the resulting struct since we do not want to store nil valued + // attributes. + c.Attributes, err = patchstruct.PatchBytes([]byte{}, c.Attributes) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + + plgHc, err := toPluginCatalog(ctx, c) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + plgClient, ok := r.plugins[c.GetPluginId()] + if !ok || plgClient == nil { + return nil, nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("plugin %q not available", c.GetPluginId())) + } + + oplogWrapper, err := r.kms.GetWrapper(ctx, c.ScopeId, kms.KeyPurposeOplog) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) + } + + // If the call to the plugin succeeded, we do not want to call it again if + // the transaction failed and is being retried. + var pluginCalledSuccessfully bool + var plgResp *plgpb.OnCreateCatalogResponse + + var newHostCatalog *HostCatalog + _, err = r.writer.DoTx( + ctx, + db.StdRetryCnt, + db.ExpBackoff{}, + func(_ db.Reader, w db.Writer) error { + msgs := make([]*oplog.Message, 0, 3) + ticket, err := w.GetTicket(c) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket")) + } + + newHostCatalog = c.clone() + var cOplogMsg oplog.Message + if err := w.Create(ctx, newHostCatalog, db.NewOplogMsg(&cOplogMsg)); err != nil { + return errors.Wrap(ctx, err, op) + } + msgs = append(msgs, &cOplogMsg) + + if !pluginCalledSuccessfully { + plgResp, err = plgClient.OnCreateCatalog(ctx, &plgpb.OnCreateCatalogRequest{Catalog: plgHc}) + if err != nil { + if status.Code(err) != codes.Unimplemented { + return errors.Wrap(ctx, err, op) + } + } + pluginCalledSuccessfully = true + } + + if plgResp != nil && plgResp.GetPersisted().GetSecrets() != nil { + hcSecret, err := newHostCatalogSecret(ctx, id, plgResp.GetPersisted().GetSecrets()) + if err != nil { + return errors.Wrap(ctx, err, op) + } + dbWrapper, err := r.kms.GetWrapper(ctx, c.ScopeId, kms.KeyPurposeDatabase) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get db wrapper")) + } + if err := hcSecret.encrypt(ctx, dbWrapper); err != nil { + return errors.Wrap(ctx, err, op) + } + if hcSecret != nil { + newSecret := hcSecret.clone() + q, v := newSecret.upsertQuery() + rows, err := w.Exec(ctx, q, v) + if err != nil { + return errors.Wrap(ctx, err, op) + } + if rows > 1 { + return errors.New(ctx, errors.MultipleRecords, op, "more than 1 catalog secret would have been created") + } + msgs = append(msgs, newSecret.oplogMessage(db.CreateOp)) + } + } + + metadata := c.oplog(oplog.OpType_OP_TYPE_CREATE) + if err := w.WriteOplogEntryWith(ctx, oplogWrapper, ticket, metadata, msgs); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog")) + } + return nil + }, + ) + + if err != nil { + if errors.IsUniqueError(err) { + return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in scope: %s: name %s already exists", c.ScopeId, c.Name))) + } + return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in scope: %s", c.ScopeId))) + } + plg, err := r.getPlugin(ctx, newHostCatalog.GetPluginId()) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + return newHostCatalog, plg, nil +} + +// UpdateCatalog updates the repository entry for c.PublicId with the +// values in c for the fields listed in fieldMask. It returns a new +// HostCatalog containing the updated values and a count of the number of +// records updated. c is not changed. +// +// c must contain a valid PublicId. c.Name, c.Description, and +// c.Attributes can be updated; if c.Secrets is present, its contents +// are sent to the plugin (along with any other changes, see below) +// before the update is sent to the database. +// +// An attribute of c will be set to NULL in the database if the +// attribute in c is the zero value and it is included in fieldMask. +// Note that this does not apply to c.Attributes - a null +// c.Attributes is a no-op for modifications. Rather, if fields need +// to be reset, its field in c.Attributes should individually set to +// null. +// +// Updates are sent to OnUpdateCatalog with a full copy of both the +// current catalog, and the state of the new catalog should it be +// updated, along with any secrets included in the new request. This +// request may alter the returned persisted state. Update of the +// record in the database is aborted if this call fails. +func (r *Repository) UpdateCatalog(ctx context.Context, c *HostCatalog, version uint32, fieldMask []string, _ ...Option) (*HostCatalog, *hostplugin.Plugin, int, error) { + const op = "plugin.(Repository).UpdateCatalog" + if c == nil { + return nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "nil HostCatalog") + } + if c.HostCatalog == nil { + return nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "nil embedded HostCatalog") + } + if c.PublicId == "" { + return nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "no public id") + } + if c.ScopeId == "" { + return nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "no scope id") + } + if len(fieldMask) == 0 { + return nil, nil, db.NoRowsAffected, errors.New(ctx, errors.EmptyFieldMask, op, "empty field mask") + } + + // Get the old catalog first. We patch the record first before + // sending it to the DB for updating so that we can run on + // OnUpdateCatalog. Note that the field masks are still used for + // updating. + currentCatalog, currentCatalogPersisted, err := r.getCatalog(ctx, c.PublicId) + if err != nil { + return nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error looking up catalog with id %q", c.PublicId))) + } + + if currentCatalog == nil { + return nil, nil, db.NoRowsAffected, errors.New(ctx, errors.RecordNotFound, op, fmt.Sprintf("catalog with id %q not found", c.PublicId)) + } + + // Assert the version of the current catalog to make sure we're + // updating the correct one. + if currentCatalog.GetVersion() != version { + return nil, nil, db.NoRowsAffected, errors.New(ctx, errors.VersionMismatch, op, fmt.Sprintf("catalog version mismatch, want=%d, got=%d", currentCatalog.GetVersion(), version)) + } + + // Clone the catalog so that we can set fields. + newCatalog := currentCatalog.clone() + var updateAttributes bool + var dbMask, nullFields []string + for _, f := range fieldMask { + switch { + case strings.EqualFold("name", f) && c.Name == "": + nullFields = append(nullFields, "name") + newCatalog.Name = c.Name + case strings.EqualFold("name", f) && c.Name != "": + dbMask = append(dbMask, "name") + newCatalog.Name = c.Name + case strings.EqualFold("description", f) && c.Description == "": + nullFields = append(nullFields, "description") + newCatalog.Description = c.Description + case strings.EqualFold("description", f) && c.Description != "": + dbMask = append(dbMask, "description") + newCatalog.Description = c.Description + case strings.EqualFold("attributes", strings.Split(f, ".")[0]): + // Flag attributes for updating. While multiple masks may be + // sent, we only need to do this once. + updateAttributes = true + case strings.EqualFold("secrets", strings.Split(f, ".")[0]): + // While in a similar format, secrets are passed along + // wholesale (for the time being). Don't append this mask + // field, as secrets do not have a database entry. + newCatalog.Secrets = c.Secrets + default: + return nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidFieldMask, op, fmt.Sprintf("invalid field mask: %s", f)) + } + } + + if updateAttributes && c.Attributes != nil { + dbMask = append(dbMask, "attributes") + newCatalog.Attributes, err = patchstruct.PatchBytes(newCatalog.Attributes, c.Attributes) + if err != nil { + return nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("error in catalog attribute JSON")) + } + } + + // Get the plugin for the host catalog - this is to return it back + // after the update is complete. We don't actually do anything with + // the record otherwise. Fetch it here so that if there's an + // integrity error, we don't call the plugin. + plg, err := r.getPlugin(ctx, currentCatalog.GetPluginId()) + if err != nil { + return nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op) + } + + // Get the plugin client. + plgClient, ok := r.plugins[currentCatalog.GetPluginId()] + if !ok || plgClient == nil { + return nil, nil, db.NoRowsAffected, errors.New(ctx, errors.Internal, op, fmt.Sprintf("plugin %q not available", currentCatalog.GetPluginId())) + } + + // Convert the catalog values to API protobuf values, which is what + // we use for the plugin hook calls. + currPlgHc, err := toPluginCatalog(ctx, currentCatalog) + if err != nil { + return nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op) + } + newPlgHc, err := toPluginCatalog(ctx, newCatalog) + if err != nil { + return nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op) + } + + dbWrapper, err := r.kms.GetWrapper(ctx, newCatalog.ScopeId, kms.KeyPurposeDatabase) + if err != nil { + return nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get db wrapper")) + } + // Get the oplog. + oplogWrapper, err := r.kms.GetWrapper(ctx, newCatalog.ScopeId, kms.KeyPurposeOplog) + if err != nil { + return nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) + } + + var pluginCalledSuccessfully bool + var plgResp *plgpb.OnUpdateCatalogResponse + + var recordUpdated bool + var returnedCatalog *HostCatalog + _, err = r.writer.DoTx( + ctx, + db.StdRetryCnt, + db.ExpBackoff{}, + func(_ db.Reader, w db.Writer) error { + msgs := make([]*oplog.Message, 0, 3) + ticket, err := w.GetTicket(newCatalog) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket")) + } + + if len(dbMask) != 0 || len(nullFields) != 0 { + returnedCatalog = newCatalog.clone() + var cOplogMsg oplog.Message + catalogsUpdated, err := w.Update( + ctx, + returnedCatalog, + dbMask, + nullFields, + db.NewOplogMsg(&cOplogMsg), + db.WithVersion(&version), + ) + if err != nil { + return errors.Wrap(ctx, err, op) + } + if catalogsUpdated != 1 { + return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("expected 1 catalog to be deleted, got %d", catalogsUpdated)) + } + msgs = append(msgs, &cOplogMsg) + recordUpdated = true + } else { + // Returned catalog needs to be the old copy, as no fields in the + // catalog itself are being updated (note: secrets may still be + // updated). + returnedCatalog = currentCatalog.clone() + } + + if !pluginCalledSuccessfully { + plgResp, err = plgClient.OnUpdateCatalog(ctx, &plgpb.OnUpdateCatalogRequest{ + CurrentCatalog: currPlgHc, + NewCatalog: newPlgHc, + Persisted: currentCatalogPersisted, + }) + if err != nil { + if status.Code(err) != codes.Unimplemented { + return errors.Wrap(ctx, err, op) + } + } + pluginCalledSuccessfully = true + } + var updatedPersisted bool + if plgResp != nil && plgResp.GetPersisted().GetSecrets() != nil { + if len(plgResp.GetPersisted().GetSecrets().GetFields()) == 0 { + // Flag the secret to be deleted. + hcSecret, err := newHostCatalogSecret(ctx, currentCatalog.GetPublicId(), plgResp.GetPersisted().GetSecrets()) + if err != nil { + return errors.Wrap(ctx, err, op) + } + // We didn't set/encrypt the persisted data because there was + // none returned. Just delete the entry. + deletedSecret := hcSecret.clone() + q, v := deletedSecret.deleteQuery() + secretsUpdated, err := w.Exec(ctx, q, v) + if err != nil { + return errors.Wrap(ctx, err, op) + } + if secretsUpdated != 1 { + return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("expected 1 catalog secret to be deleted, got %d", secretsUpdated)) + } + updatedPersisted = true + msgs = append(msgs, deletedSecret.oplogMessage(db.DeleteOp)) + } else { + hcSecret, err := newHostCatalogSecret(ctx, currentCatalog.GetPublicId(), plgResp.GetPersisted().GetSecrets()) + if err != nil { + return errors.Wrap(ctx, err, op) + } + if err := hcSecret.encrypt(ctx, dbWrapper); err != nil { + return errors.Wrap(ctx, err, op) + } + + // Update the secrets. + updatedSecret := hcSecret.clone() + q, v := updatedSecret.upsertQuery() + secretsUpdated, err := w.Exec(ctx, q, v) + if err != nil { + return errors.Wrap(ctx, err, op) + } + if secretsUpdated != 1 { + return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("expected 1 catalog secret to be updated, got %d", secretsUpdated)) + } + updatedPersisted = true + msgs = append(msgs, updatedSecret.oplogMessage(db.UpdateOp)) + } + } + + if !recordUpdated && updatedPersisted { + // we only updated secrets, so we need to increment the + // version of the host catalog manually. + returnedCatalog = newCatalog.clone() + returnedCatalog.Version = uint32(version) + 1 + var cOplogMsg oplog.Message + catalogsUpdated, err := w.Update( + ctx, + returnedCatalog, + []string{"version"}, + []string{}, + db.NewOplogMsg(&cOplogMsg), + db.WithVersion(&version), + ) + if err != nil { + return errors.Wrap(ctx, err, op) + } + if catalogsUpdated != 1 { + return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("expected 1 catalog to be updated, got %d", catalogsUpdated)) + } + msgs = append(msgs, &cOplogMsg) + recordUpdated = true + } + + if len(msgs) != 0 { + metadata := newCatalog.oplog(oplog.OpType_OP_TYPE_UPDATE) + if err := w.WriteOplogEntryWith(ctx, oplogWrapper, ticket, metadata, msgs); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog")) + } + } + + return nil + }, + ) + + if err != nil { + if errors.IsUniqueError(err) { + return nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in %s: name %s already exists", newCatalog.PublicId, newCatalog.Name))) + } + return nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in %s", newCatalog.PublicId))) + } + + // Even if we didn't update any records, if we were able to find the record + // with the appropriate version, returning 1 row updated is consistant with + // static's update catalog behavior. + numUpdated := 1 + + return returnedCatalog, plg, numUpdated, nil +} + +// LookupCatalog returns the HostCatalog for id. Returns nil, nil if no +// HostCatalog is found for id. +func (r *Repository) LookupCatalog(ctx context.Context, id string, _ ...Option) (*HostCatalog, *hostplugin.Plugin, error) { + const op = "plugin.(Repository).LookupCatalog" + if id == "" { + return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "no public id") + } + c, _, err := r.getCatalog(ctx, id) + if errors.IsNotFoundError(err) { + return nil, nil, nil + } + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for: %s", id))) + } + plg, err := r.getPlugin(ctx, c.GetPluginId()) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + return c, plg, nil +} + +// ListCatalogs returns a slice of HostCatalogs for the scope IDs. WithLimit is the only option supported. +func (r *Repository) ListCatalogs(ctx context.Context, scopeIds []string, opt ...host.Option) ([]*HostCatalog, []*hostplugin.Plugin, error) { + const op = "plugin.(Repository).ListCatalogs" + if len(scopeIds) == 0 { + return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "no scope id") + } + opts, err := host.GetOpts(opt...) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + limit := r.defaultLimit + if opts.WithLimit != 0 { + // non-zero signals an override of the default limit for the repo. + limit = opts.WithLimit + } + var hostCatalogs []*HostCatalog + if err := r.reader.SearchWhere(ctx, &hostCatalogs, "scope_id in (?)", []interface{}{scopeIds}, db.WithLimit(limit)); err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + plgIds := make([]string, 0, len(hostCatalogs)) + for _, c := range hostCatalogs { + plgIds = append(plgIds, c.PluginId) + } + var plgs []*hostplugin.Plugin + if err := r.reader.SearchWhere(ctx, &plgs, "public_id in (?)", []interface{}{plgIds}); err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + return hostCatalogs, plgs, nil +} + +// DeleteCatalog deletes catalog for the provided id from the repository +// returning a count of the number of records deleted. All options are ignored. +func (r *Repository) DeleteCatalog(ctx context.Context, id string, _ ...Option) (int, error) { + const op = "plugin.(Repository).DeleteCatalog" + if id == "" { + return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "no public id") + } + + c, p, err := r.getCatalog(ctx, id) + if err != nil && !errors.IsNotFoundError(err) { + return db.NoRowsAffected, errors.Wrap(ctx, err, op) + } + if c == nil { + return db.NoRowsAffected, nil + } + plgHc, err := toPluginCatalog(ctx, c) + if err != nil { + return db.NoRowsAffected, errors.Wrap(ctx, err, op) + } + sets, _, err := r.getSets(ctx, "", c.GetPublicId()) + if err != nil { + return db.NoRowsAffected, errors.Wrap(ctx, err, op) + } + + var plgSets []*pbset.HostSet + for _, s := range sets { + ps, err := toPluginSet(ctx, s) + if err != nil { + return db.NoRowsAffected, errors.Wrap(ctx, err, op) + } + plgSets = append(plgSets, ps) + } + + plgClient, ok := r.plugins[c.GetPluginId()] + if !ok || plgClient == nil { + return 0, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("plugin %q not available", c.GetPluginId())) + } + _, err = plgClient.OnDeleteCatalog(ctx, &plgpb.OnDeleteCatalogRequest{ + Catalog: plgHc, + Sets: plgSets, + Persisted: p, + }) + if err != nil { + // Even if the plugin returns an error, we ignore it and proceed with + // deleting the catalog. + event.WriteError(ctx, op, err, event.WithInfoMsg("plugin deleting catalog", "host plugin id", c.GetPluginId())) + } + + oplogWrapper, err := r.kms.GetWrapper(ctx, c.ScopeId, kms.KeyPurposeOplog) + if err != nil { + return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) + } + metadata := c.oplog(oplog.OpType_OP_TYPE_DELETE) + + var rowsDeleted int + var deleteCatalog *HostCatalog + _, err = r.writer.DoTx( + ctx, + db.StdRetryCnt, + db.ExpBackoff{}, + func(_ db.Reader, w db.Writer) error { + deleteCatalog = c.clone() + var err error + rowsDeleted, err = w.Delete( + ctx, + deleteCatalog, + db.WithOplog(oplogWrapper, metadata), + ) + if err != nil { + return errors.Wrap(ctx, err, op) + } + if rowsDeleted > 1 { + return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted") + } + return nil + }, + ) + + if err != nil { + return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("delete failed for %s", c.PublicId))) + } + + return rowsDeleted, nil +} + +// getCatalog retrieves the *HostCatalog with the provided id. If it is not found or there +// is an problem getting it from the database an error is returned instead. +func (r *Repository) getCatalog(ctx context.Context, id string) (*HostCatalog, *plgpb.HostCatalogPersisted, error) { + const op = "plugin.(Repository).getCatalog" + ca := &catalogAgg{} + ca.PublicId = id + if err := r.reader.LookupByPublicId(ctx, ca); err != nil { + return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for: %s", id))) + } + c, s := ca.toCatalogAndPersisted() + var p *plgpb.HostCatalogPersisted + if s != nil { + var err error + p, err = toPluginPersistedData(ctx, r.kms, c.GetScopeId(), s) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + } + return c, p, nil +} + +func (r *Repository) getPlugin(ctx context.Context, plgId string) (*hostplugin.Plugin, error) { + const op = "plugin.(Repository).getPlugin" + if plgId == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "no plugin id") + } + plg := hostplugin.NewPlugin() + plg.PublicId = plgId + if err := r.reader.LookupByPublicId(ctx, plg); err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to get host plugin with id %q", plgId))) + } + return plg, nil +} + +// toPluginCatalog returns a host catalog, with it's secret if available, in the format expected +// by the host plugin system. +func toPluginCatalog(ctx context.Context, in *HostCatalog) (*pb.HostCatalog, error) { + const op = "plugin.toPluginCatalog" + if in == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "nil storage plugin") + } + var name, description *wrapperspb.StringValue + if inName := in.GetName(); inName != "" { + name = wrapperspb.String(inName) + } + if inDescription := in.GetDescription(); inDescription != "" { + description = wrapperspb.String(inDescription) + } + + hc := &pb.HostCatalog{ + Id: in.GetPublicId(), + ScopeId: in.GetScopeId(), + Name: name, + Description: description, + } + if in.GetAttributes() != nil { + attrs := &structpb.Struct{} + if err := proto.Unmarshal(in.GetAttributes(), attrs); err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to unmarshal attributes")) + } + hc.Attributes = attrs + } + if in.Secrets != nil { + hc.Secrets = in.Secrets + } + return hc, nil +} + +// toPluginCatalog converts a *HostCatalogSecret from storage to a +// *plgpb.HostCatalogPersisted expected by a plugin. Scope Id must be set. +func toPluginPersistedData(ctx context.Context, kmsCache *kms.Kms, scopeId string, cSecret *HostCatalogSecret) (*plgpb.HostCatalogPersisted, error) { + const op = "plugin.(Repository).getPersistedDataForCatalog" + if scopeId == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "empty scope id") + } + if cSecret == nil { + return nil, nil + } + dbWrapper, err := kmsCache.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get db wrapper")) + } + if err := cSecret.decrypt(ctx, dbWrapper); err != nil { + return nil, errors.Wrap(ctx, err, op) + } + + secrets := &structpb.Struct{} + if err := proto.Unmarshal(cSecret.GetSecret(), secrets); err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unmarshaling secret")) + } + return &plgpb.HostCatalogPersisted{Secrets: secrets}, nil +} diff --git a/internal/host/plugin/repository_host_catalog_test.go b/internal/host/plugin/repository_host_catalog_test.go new file mode 100644 index 000000000..832a07964 --- /dev/null +++ b/internal/host/plugin/repository_host_catalog_test.go @@ -0,0 +1,1446 @@ +package plugin + +import ( + "context" + "fmt" + "strings" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/host/plugin/store" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/oplog" + hostplg "github.com/hashicorp/boundary/internal/plugin/host" + hostplugin "github.com/hashicorp/boundary/internal/plugin/host" + "github.com/hashicorp/boundary/internal/scheduler" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +func TestRepository_CreateCatalog(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + sched := scheduler.TestScheduler(t, conn, wrapper) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + plg := hostplg.TestPlugin(t, conn, "test") + unimplementedPlugin := hostplg.TestPlugin(t, conn, "unimplemented") + + tests := []struct { + name string + in *HostCatalog + opts []Option + want *HostCatalog + wantPluginCalled bool + wantSecret *structpb.Struct + wantIsErr errors.Code + }{ + { + name: "nil-catalog", + wantIsErr: errors.InvalidParameter, + }, + { + name: "nil-embedded-catalog", + in: &HostCatalog{}, + wantIsErr: errors.InvalidParameter, + }, + { + name: "no-scope", + in: &HostCatalog{ + HostCatalog: &store.HostCatalog{ + PluginId: plg.GetPublicId(), + Attributes: []byte{}, + }, + }, + wantIsErr: errors.InvalidParameter, + }, + { + name: "no-plugin", + in: &HostCatalog{ + HostCatalog: &store.HostCatalog{ + ScopeId: prj.GetPublicId(), + Attributes: []byte{}, + }, + }, + wantIsErr: errors.InvalidParameter, + }, + { + name: "no-attributes", + in: &HostCatalog{ + HostCatalog: &store.HostCatalog{ + ScopeId: prj.GetPublicId(), + PluginId: plg.GetPublicId(), + }, + }, + wantIsErr: errors.InvalidParameter, + }, + { + name: "valid-no-options", + in: &HostCatalog{ + HostCatalog: &store.HostCatalog{ + ScopeId: prj.GetPublicId(), + PluginId: plg.GetPublicId(), + Attributes: []byte{}, + }, + }, + want: &HostCatalog{ + HostCatalog: &store.HostCatalog{ + ScopeId: prj.GetPublicId(), + PluginId: plg.GetPublicId(), + Attributes: []byte{}, + }, + }, + wantPluginCalled: true, + }, + { + name: "valid-unimplemented-plugin", + in: &HostCatalog{ + HostCatalog: &store.HostCatalog{ + ScopeId: prj.GetPublicId(), + PluginId: unimplementedPlugin.GetPublicId(), + Attributes: []byte{}, + }, + }, + want: &HostCatalog{ + HostCatalog: &store.HostCatalog{ + ScopeId: prj.GetPublicId(), + PluginId: unimplementedPlugin.GetPublicId(), + Attributes: []byte{}, + }, + }, + wantPluginCalled: true, + }, + { + name: "not-found-plugin", + in: &HostCatalog{ + HostCatalog: &store.HostCatalog{ + ScopeId: prj.GetPublicId(), + PluginId: "unknown_plugin", + Attributes: []byte{}, + }, + }, + wantIsErr: errors.InvalidParameter, + }, + { + name: "valid-with-name", + in: &HostCatalog{ + HostCatalog: &store.HostCatalog{ + Name: "test-name-repo", + ScopeId: prj.GetPublicId(), + PluginId: plg.GetPublicId(), + Attributes: []byte{}, + }, + }, + want: &HostCatalog{ + HostCatalog: &store.HostCatalog{ + Name: "test-name-repo", + ScopeId: prj.GetPublicId(), + PluginId: plg.GetPublicId(), + Attributes: []byte{}, + }, + }, + wantPluginCalled: true, + }, + { + name: "valid-with-description", + in: &HostCatalog{ + HostCatalog: &store.HostCatalog{ + Description: "test-description-repo", + ScopeId: prj.GetPublicId(), + PluginId: plg.GetPublicId(), + Attributes: []byte{}, + }, + }, + want: &HostCatalog{ + HostCatalog: &store.HostCatalog{ + Description: "test-description-repo", + ScopeId: prj.GetPublicId(), + PluginId: plg.GetPublicId(), + Attributes: []byte{}, + }, + }, + wantPluginCalled: true, + }, + { + name: "valid-with-attributes", + in: &HostCatalog{ + HostCatalog: &store.HostCatalog{ + ScopeId: prj.GetPublicId(), + PluginId: plg.GetPublicId(), + Attributes: func() []byte { + st, err := structpb.NewStruct(map[string]interface{}{ + "k1": "foo", + "nilkey": nil, + }) + require.NoError(t, err) + b, err := proto.Marshal(st) + require.NoError(t, err) + return b + }(), + }, + }, + want: &HostCatalog{ + HostCatalog: &store.HostCatalog{ + ScopeId: prj.GetPublicId(), + PluginId: plg.GetPublicId(), + Attributes: func() []byte { + st, err := structpb.NewStruct(map[string]interface{}{"k1": "foo"}) + require.NoError(t, err) + b, err := proto.Marshal(st) + require.NoError(t, err) + return b + }(), + }, + }, + wantPluginCalled: true, + }, + { + name: "valid-with-secret", + in: &HostCatalog{ + HostCatalog: &store.HostCatalog{ + Description: "test-description-repo", + ScopeId: prj.GetPublicId(), + PluginId: plg.GetPublicId(), + Attributes: []byte{}, + }, + Secrets: func() *structpb.Struct { + st, err := structpb.NewStruct(map[string]interface{}{ + "k1": "v1", + "k2": 2, + "k3": nil, + }) + require.NoError(t, err) + return st + }(), + }, + want: &HostCatalog{ + HostCatalog: &store.HostCatalog{ + Description: "test-description-repo", + ScopeId: prj.GetPublicId(), + PluginId: plg.GetPublicId(), + Attributes: []byte{}, + }, + }, + wantSecret: func() *structpb.Struct { + st, err := structpb.NewStruct(map[string]interface{}{ + "k1": "v1", + "k2": 2, + "k3": nil, + }) + require.NoError(t, err) + return st + }(), + wantPluginCalled: true, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + kmsCache := kms.TestKms(t, conn, wrapper) + + // gotPluginAttrs tracks which attributes a plugin has received through a closure and can be compared in the + // test against the expected values sent to the plugin. + var gotPluginAttrs *structpb.Struct + var pluginCalled bool + plgm := map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): &WrappingPluginClient{ + Server: &TestPluginServer{ + OnCreateCatalogFn: func(_ context.Context, req *plgpb.OnCreateCatalogRequest) (*plgpb.OnCreateCatalogResponse, error) { + pluginCalled = true + gotPluginAttrs = req.GetCatalog().GetAttributes() + return &plgpb.OnCreateCatalogResponse{Persisted: &plgpb.HostCatalogPersisted{Secrets: req.GetCatalog().GetSecrets()}}, nil + }, + }, + }, + unimplementedPlugin.GetPublicId(): &WrappingPluginClient{Server: &TestPluginServer{ + OnCreateCatalogFn: func(ctx context.Context, req *plgpb.OnCreateCatalogRequest) (*plgpb.OnCreateCatalogResponse, error) { + pluginCalled = true + gotPluginAttrs = req.GetCatalog().GetAttributes() + return plgpb.UnimplementedHostPluginServiceServer{}.OnCreateCatalog(ctx, req) + }, + }}, + } + repo, err := NewRepository(rw, rw, kmsCache, sched, plgm) + assert.NoError(err) + assert.NotNil(repo) + got, _, err := repo.CreateCatalog(ctx, tt.in, tt.opts...) + assert.Equal(tt.wantPluginCalled, pluginCalled) + if tt.wantIsErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "want err: %q got: %q", tt.wantIsErr, err) + assert.Nil(got) + return + } + require.NoError(t, err) + assert.Empty(tt.in.PublicId) + assert.NotNil(got) + assertPluginBasedPublicId(t, HostCatalogPrefix, got.PublicId) + assert.NotSame(tt.in, got) + assert.Equal(tt.want.Name, got.Name) + assert.Equal(tt.want.Description, got.Description) + assert.Equal(got.CreateTime, got.UpdateTime) + + // wantedPluginAttributes := &structpb.Struct{} + // require.NoError(t, proto.Unmarshal(tt.want.GetAttributes(), wantedPluginAttributes)) + gotB, err := proto.Marshal(gotPluginAttrs) + require.NoError(t, err) + assert.Equal(tt.want.GetAttributes(), gotB) + + assert.NoError(db.TestVerifyOplog(t, rw, got.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))) + + cSecret := allocHostCatalogSecret() + err = rw.LookupWhere(ctx, &cSecret, "catalog_id=?", got.GetPublicId()) + if tt.wantSecret == nil { + assert.Nil(got.Secrets) + require.Error(t, err) + require.True(t, errors.IsNotFoundError(err)) + return + } + require.NoError(t, err) + require.Empty(t, cSecret.Secret) + require.NotEmpty(t, cSecret.CtSecret) + + dbWrapper, err := kmsCache.GetWrapper(ctx, got.GetScopeId(), kms.KeyPurposeDatabase) + require.NoError(t, err) + require.NoError(t, cSecret.decrypt(ctx, dbWrapper)) + + st := &structpb.Struct{} + require.NoError(t, proto.Unmarshal(cSecret.Secret, st)) + assert.Empty(cmp.Diff(tt.wantSecret, st, protocmp.Transform())) + }) + } + + t.Run("invalid-duplicate-names", func(t *testing.T) { + assert := assert.New(t) + kms := kms.TestKms(t, conn, wrapper) + var pluginCalled bool + plgm := map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): &WrappingPluginClient{ + Server: &TestPluginServer{ + OnCreateCatalogFn: func(_ context.Context, req *plgpb.OnCreateCatalogRequest) (*plgpb.OnCreateCatalogResponse, error) { + pluginCalled = true + return &plgpb.OnCreateCatalogResponse{Persisted: &plgpb.HostCatalogPersisted{Secrets: req.GetCatalog().GetSecrets()}}, nil + }, + }, + }, + } + repo, err := NewRepository(rw, rw, kms, sched, plgm) + assert.NoError(err) + assert.NotNil(repo) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + in := &HostCatalog{ + HostCatalog: &store.HostCatalog{ + ScopeId: prj.GetPublicId(), + Name: "test-name-repo", + PluginId: plg.GetPublicId(), + Attributes: []byte{}, + }, + } + + got, _, err := repo.CreateCatalog(context.Background(), in) + assert.NoError(err) + assert.NotNil(got) + assert.True(pluginCalled) + assertPluginBasedPublicId(t, HostCatalogPrefix, got.PublicId) + assert.NotSame(in, got) + assert.Equal(in.Name, got.Name) + assert.Equal(in.Description, got.Description) + assert.Equal(got.CreateTime, got.UpdateTime) + + // Reset pluginCalled so we can see that the plugin wasn't called w/ + // the duplicate name. + pluginCalled = false + got2, _, err := repo.CreateCatalog(context.Background(), in) + assert.False(pluginCalled) + assert.Truef(errors.Match(errors.T(errors.NotUnique), err), "want err code: %v got err: %v", errors.NotUnique, err) + assert.Nil(got2) + }) + + t.Run("valid-duplicate-names-diff-scopes", func(t *testing.T) { + assert := assert.New(t) + kms := kms.TestKms(t, conn, wrapper) + var pluginCalled bool + plgm := map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): &WrappingPluginClient{ + Server: &TestPluginServer{ + OnCreateCatalogFn: func(_ context.Context, req *plgpb.OnCreateCatalogRequest) (*plgpb.OnCreateCatalogResponse, error) { + pluginCalled = true + return &plgpb.OnCreateCatalogResponse{Persisted: &plgpb.HostCatalogPersisted{Secrets: req.GetCatalog().GetSecrets()}}, nil + }, + }, + }, + } + repo, err := NewRepository(rw, rw, kms, sched, plgm) + assert.NoError(err) + assert.NotNil(repo) + org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + in := &HostCatalog{ + HostCatalog: &store.HostCatalog{ + Name: "test-name-repo", + PluginId: plg.GetPublicId(), + Attributes: []byte{}, + }, + } + in2 := in.clone() + + in.ScopeId = prj.GetPublicId() + got, _, err := repo.CreateCatalog(context.Background(), in) + assert.NoError(err) + assert.NotNil(got) + assert.True(pluginCalled) + assertPluginBasedPublicId(t, HostCatalogPrefix, got.PublicId) + assert.NotSame(in, got) + assert.Equal(in.Name, got.Name) + assert.Equal(in.Description, got.Description) + assert.Equal(got.CreateTime, got.UpdateTime) + + in2.ScopeId = org.GetPublicId() + pluginCalled = false // reset pluginCalled for this next call to Create + got2, _, err := repo.CreateCatalog(context.Background(), in2) + assert.NoError(err) + assert.NotNil(got2) + assert.True(pluginCalled) + assertPluginBasedPublicId(t, HostCatalogPrefix, got2.PublicId) + assert.NotSame(in2, got2) + assert.Equal(in2.Name, got2.Name) + assert.Equal(in2.Description, got2.Description) + assert.Equal(got2.CreateTime, got2.UpdateTime) + }) +} + +func TestRepository_UpdateCatalog(t *testing.T) { + ctx := context.Background() + dbConn, _ := db.TestSetup(t, "postgres") + dbRW := db.New(dbConn) + dbWrapper := db.TestWrapper(t) + sched := scheduler.TestScheduler(t, dbConn, dbWrapper) + dbKmsCache := kms.TestKms(t, dbConn, dbWrapper) + _, projectScope := iam.TestScopes(t, iam.TestRepo(t, dbConn, dbWrapper)) + _, projectScopeAlt := iam.TestScopes(t, iam.TestRepo(t, dbConn, dbWrapper)) + + // Define a plugin "manager", basically just a map with a mock + // plugin in it. This also includes functionality to capture the + // state, and set an error and the returned secrets to nil. Note + // that the way that this is set up means that the tests cannot run + // in parallel, but there could be other factors affecting that as + // well. + var setRespSecretsNil bool + var gotOnUpdateCatalogRequest *plgpb.OnUpdateCatalogRequest + var pluginError error + testPlugin := hostplg.TestPlugin(t, dbConn, "test") + testPluginMap := map[string]plgpb.HostPluginServiceClient{ + testPlugin.GetPublicId(): &WrappingPluginClient{ + Server: &TestPluginServer{ + OnUpdateCatalogFn: func(_ context.Context, req *plgpb.OnUpdateCatalogRequest) (*plgpb.OnUpdateCatalogResponse, error) { + gotOnUpdateCatalogRequest = req + respSecrets := req.GetNewCatalog().GetSecrets() + if setRespSecretsNil { + respSecrets = nil + setRespSecretsNil = false + } + + return &plgpb.OnUpdateCatalogResponse{Persisted: &plgpb.HostCatalogPersisted{Secrets: respSecrets}}, pluginError + }, + }, + }, + } + + // Set up two existing catalogs for duplicate tests, one in the + // same scope that we are working in mostly, and another in a + // different one. Both are project scopes. + const ( + testDuplicateCatalogNameProjectScope = "duplicate-catalog-name-project-scope" + testDuplicateCatalogNameAltScope = "duplicate-catalog-name-alt-scope" + ) + + // Project scope + existingProjectScopeCatalog := TestCatalog(t, dbConn, projectScope.PublicId, testPlugin.GetPublicId()) + existingProjectScopeCatalog.Name = testDuplicateCatalogNameProjectScope + numCatUpdated, err := dbRW.Update(ctx, existingProjectScopeCatalog, []string{"name"}, []string{}) + require.NoError(t, err) + require.Equal(t, 1, numCatUpdated) + + // Alternate scope + existingAltScopeCatalog := TestCatalog(t, dbConn, projectScopeAlt.PublicId, testPlugin.GetPublicId()) + existingAltScopeCatalog.Name = testDuplicateCatalogNameAltScope + numCatUpdated, err = dbRW.Update(ctx, existingAltScopeCatalog, []string{"name"}, []string{}) + require.NoError(t, err) + require.Equal(t, 1, numCatUpdated) + + // Define some helpers here to make the test table more readable. + type changeHostCatalogFunc func(c *HostCatalog) *HostCatalog + + changePublicId := func(s string) changeHostCatalogFunc { + return func(c *HostCatalog) *HostCatalog { + c.PublicId = s + return c + } + } + + changeScopeId := func(s string) changeHostCatalogFunc { + return func(c *HostCatalog) *HostCatalog { + c.ScopeId = s + return c + } + } + + changeName := func(s string) changeHostCatalogFunc { + return func(c *HostCatalog) *HostCatalog { + c.Name = s + return c + } + } + + changeDescription := func(s string) changeHostCatalogFunc { + return func(c *HostCatalog) *HostCatalog { + c.Description = s + return c + } + } + + changeAttributes := func(m map[string]interface{}) changeHostCatalogFunc { + return func(c *HostCatalog) *HostCatalog { + c.Attributes = mustMarshal(m) + return c + } + } + + changeSecrets := func(m map[string]interface{}) changeHostCatalogFunc { + return func(c *HostCatalog) *HostCatalog { + c.Secrets = mustStruct(m) + return c + } + } + + changeCatalogToNil := func() changeHostCatalogFunc { + return func(_ *HostCatalog) *HostCatalog { + return nil + } + } + + changeEmbeddedCatalogToNil := func() changeHostCatalogFunc { + return func(c *HostCatalog) *HostCatalog { + c.HostCatalog = nil + return c + } + } + + // Define some checks that will be used in the below tests. Some of + // these are re-used, so we define them here. Most of these are + // assertions and no particular one is non-fatal in that they will + // stop execution. Note that these are executed after wantIsErr, so + // if that is set in an individual table test, these will not be + // executed. + // + // Note that we define some state here, similar to how we + // previously defined gotOnUpdateCatalogRequest above next the + // plugin map. + type checkFunc func(t *testing.T, ctx context.Context) + var ( + gotCatalog *HostCatalog + gotNumUpdated int + ) + + checkName := func(want string) checkFunc { + return func(t *testing.T, ctx context.Context) { + t.Helper() + assert := assert.New(t) + assert.Equal(want, gotCatalog.Name) + } + } + + checkDescription := func(want string) checkFunc { + return func(t *testing.T, ctx context.Context) { + t.Helper() + assert := assert.New(t) + assert.Equal(want, gotCatalog.Description) + } + } + + checkVersion := func(want uint32) checkFunc { + return func(t *testing.T, ctx context.Context) { + t.Helper() + assert := assert.New(t) + assert.Equal(want, gotCatalog.Version) + } + } + + checkAttributes := func(want map[string]interface{}) checkFunc { + return func(t *testing.T, ctx context.Context) { + t.Helper() + assert := assert.New(t) + require := require.New(t) + st := &structpb.Struct{} + require.NoError(proto.Unmarshal(gotCatalog.Attributes, st)) + assert.Empty(cmp.Diff(mustStruct(want), st, protocmp.Transform())) + } + } + + checkSecrets := func(want map[string]interface{}) checkFunc { + return func(t *testing.T, ctx context.Context) { + t.Helper() + assert := assert.New(t) + require := require.New(t) + + cSecret := allocHostCatalogSecret() + err := dbRW.LookupWhere(ctx, &cSecret, "catalog_id=?", gotCatalog.GetPublicId()) + require.NoError(err) + require.Empty(cSecret.Secret) + require.NotEmpty(cSecret.CtSecret) + + dbWrapper, err := dbKmsCache.GetWrapper(ctx, gotCatalog.GetScopeId(), kms.KeyPurposeDatabase) + require.NoError(err) + require.NoError(cSecret.decrypt(ctx, dbWrapper)) + + st := &structpb.Struct{} + require.NoError(proto.Unmarshal(cSecret.Secret, st)) + assert.Empty(cmp.Diff(mustStruct(want), st, protocmp.Transform())) + } + } + + checkSecretsDeleted := func() checkFunc { + return func(t *testing.T, ctx context.Context) { + t.Helper() + assert := assert.New(t) + + cSecret := allocHostCatalogSecret() + err := dbRW.LookupWhere(ctx, &cSecret, "catalog_id=?", gotCatalog.GetPublicId()) + assert.Error(err) + assert.True(errors.IsNotFoundError(err)) + } + } + + checkUpdateCatalogRequestCurrentNameNil := func() checkFunc { + return func(t *testing.T, ctx context.Context) { + t.Helper() + assert := assert.New(t) + assert.Nil(gotOnUpdateCatalogRequest.CurrentCatalog.Name) + } + } + + checkUpdateCatalogRequestNewName := func(want string) checkFunc { + return func(t *testing.T, ctx context.Context) { + t.Helper() + assert := assert.New(t) + assert.Equal(wrapperspb.String(want), gotOnUpdateCatalogRequest.NewCatalog.Name) + } + } + + checkUpdateCatalogRequestNewNameNil := func() checkFunc { + return func(t *testing.T, ctx context.Context) { + t.Helper() + assert := assert.New(t) + assert.Nil(gotOnUpdateCatalogRequest.NewCatalog.Name) + } + } + + checkUpdateCatalogRequestCurrentDescriptionNil := func() checkFunc { + return func(t *testing.T, ctx context.Context) { + t.Helper() + assert := assert.New(t) + assert.Nil(gotOnUpdateCatalogRequest.CurrentCatalog.Description) + } + } + + checkUpdateCatalogRequestNewDescription := func(want string) checkFunc { + return func(t *testing.T, ctx context.Context) { + t.Helper() + assert := assert.New(t) + assert.Equal(wrapperspb.String(want), gotOnUpdateCatalogRequest.NewCatalog.Description) + } + } + + checkUpdateCatalogRequestNewDescriptionNil := func() checkFunc { + return func(t *testing.T, ctx context.Context) { + t.Helper() + assert := assert.New(t) + assert.Nil(gotOnUpdateCatalogRequest.NewCatalog.Description) + } + } + + checkUpdateCatalogRequestCurrentAttributes := func(want map[string]interface{}) checkFunc { + return func(t *testing.T, ctx context.Context) { + t.Helper() + assert := assert.New(t) + assert.Empty(cmp.Diff(mustStruct(want), gotOnUpdateCatalogRequest.CurrentCatalog.Attributes, protocmp.Transform())) + } + } + + checkUpdateCatalogRequestNewAttributes := func(want map[string]interface{}) checkFunc { + return func(t *testing.T, ctx context.Context) { + t.Helper() + assert := assert.New(t) + assert.Empty(cmp.Diff(mustStruct(want), gotOnUpdateCatalogRequest.NewCatalog.Attributes, protocmp.Transform())) + } + } + + checkUpdateCatalogRequestPersistedSecrets := func(want map[string]interface{}) checkFunc { + return func(t *testing.T, ctx context.Context) { + t.Helper() + assert := assert.New(t) + assert.Empty(cmp.Diff(mustStruct(want), gotOnUpdateCatalogRequest.Persisted.Secrets, protocmp.Transform())) + } + } + + checkUpdateCatalogRequestSecrets := func(want map[string]interface{}) checkFunc { + return func(t *testing.T, ctx context.Context) { + t.Helper() + assert := assert.New(t) + assert.Empty(cmp.Diff(mustStruct(want), gotOnUpdateCatalogRequest.NewCatalog.Secrets, protocmp.Transform())) + // Ensure that the current catalog's secrets value is always zero + assert.Zero(gotOnUpdateCatalogRequest.CurrentCatalog.Secrets) + } + } + + checkNumUpdated := func(want int) checkFunc { + return func(t *testing.T, ctx context.Context) { + t.Helper() + assert := assert.New(t) + assert.Equal(want, gotNumUpdated) + } + } + + checkVerifyCatalogOplog := func(op oplog.OpType) checkFunc { + return func(t *testing.T, ctx context.Context) { + t.Helper() + assert := assert.New(t) + assert.NoError( + db.TestVerifyOplog( + t, + dbRW, + gotCatalog.PublicId, + db.WithOperation(op), + db.WithCreateNotBefore(10*time.Second), + ), + ) + } + } + + tests := []struct { + name string + withEmptyPluginMap bool + withRespSecretsNil bool + withPluginError error + changeFuncs []changeHostCatalogFunc + version uint32 + fieldMask []string + wantCheckFuncs []checkFunc + wantIsErr errors.Code + }{ + { + name: "nil catalog", + changeFuncs: []changeHostCatalogFunc{changeCatalogToNil()}, + wantIsErr: errors.InvalidParameter, + }, + { + name: "nil embedded catalog", + changeFuncs: []changeHostCatalogFunc{changeEmbeddedCatalogToNil()}, + wantIsErr: errors.InvalidParameter, + }, + { + name: "missing public id", + changeFuncs: []changeHostCatalogFunc{changePublicId("")}, + wantIsErr: errors.InvalidParameter, + }, + { + name: "missing scope id", + changeFuncs: []changeHostCatalogFunc{changeScopeId("")}, + wantIsErr: errors.InvalidParameter, + }, + { + name: "empty field mask", + fieldMask: nil, // Should be testing on len + wantIsErr: errors.EmptyFieldMask, + }, + { + name: "bad catalog id", + changeFuncs: []changeHostCatalogFunc{changePublicId("badid")}, + fieldMask: []string{"name"}, + wantIsErr: errors.RecordNotFound, + }, + { + name: "version mismatch", + changeFuncs: []changeHostCatalogFunc{changeName("foo")}, + version: 1, + fieldMask: []string{"name"}, + wantIsErr: errors.VersionMismatch, + }, + { + name: "plugin lookup error", + withEmptyPluginMap: true, + version: 2, + fieldMask: []string{"name"}, + wantIsErr: errors.Internal, + }, + { + name: "plugin invocation error", + withPluginError: errors.New(context.Background(), errors.Internal, "TestRepository_UpdateCatalog/plugin_invocation_error", "test plugin error"), + version: 2, + fieldMask: []string{"name"}, + wantIsErr: errors.Internal, + }, + { + name: "update name (duplicate, same scope)", + changeFuncs: []changeHostCatalogFunc{changeName(testDuplicateCatalogNameProjectScope)}, + version: 2, + fieldMask: []string{"name"}, + wantIsErr: errors.NotUnique, + }, + { + name: "update name", + changeFuncs: []changeHostCatalogFunc{changeName("foo")}, + version: 2, + fieldMask: []string{"name"}, + wantCheckFuncs: []checkFunc{ + checkVersion(3), + checkUpdateCatalogRequestCurrentNameNil(), + checkUpdateCatalogRequestNewName("foo"), + checkName("foo"), + checkSecrets(map[string]interface{}{ + "one": "two", + }), + checkNumUpdated(1), + checkVerifyCatalogOplog(oplog.OpType_OP_TYPE_UPDATE), + }, + }, + { + name: "update name to same", + changeFuncs: []changeHostCatalogFunc{changeName("")}, + version: 2, + fieldMask: []string{"name"}, + wantCheckFuncs: []checkFunc{ + checkVersion(2), // Version remains same even though row is updated + checkUpdateCatalogRequestCurrentNameNil(), + checkUpdateCatalogRequestNewNameNil(), + checkName(""), + checkSecrets(map[string]interface{}{ + "one": "two", + }), + checkNumUpdated(1), + checkVerifyCatalogOplog(oplog.OpType_OP_TYPE_UPDATE), + }, + }, + { + name: "update name (duplicate, different scope)", + changeFuncs: []changeHostCatalogFunc{changeName(testDuplicateCatalogNameAltScope)}, + version: 2, + fieldMask: []string{"name"}, + wantCheckFuncs: []checkFunc{ + checkVersion(3), + checkUpdateCatalogRequestCurrentNameNil(), + checkUpdateCatalogRequestNewName(testDuplicateCatalogNameAltScope), + checkName(testDuplicateCatalogNameAltScope), + checkSecrets(map[string]interface{}{ + "one": "two", + }), + checkNumUpdated(1), + checkVerifyCatalogOplog(oplog.OpType_OP_TYPE_UPDATE), + }, + }, + { + name: "update description", + changeFuncs: []changeHostCatalogFunc{changeDescription("foo")}, + version: 2, + fieldMask: []string{"description"}, + wantCheckFuncs: []checkFunc{ + checkVersion(3), + checkUpdateCatalogRequestCurrentDescriptionNil(), + checkUpdateCatalogRequestNewDescription("foo"), + checkDescription("foo"), + checkSecrets(map[string]interface{}{ + "one": "two", + }), + checkNumUpdated(1), + checkVerifyCatalogOplog(oplog.OpType_OP_TYPE_UPDATE), + }, + }, + { + name: "update description to same", + changeFuncs: []changeHostCatalogFunc{changeDescription("")}, + version: 2, + fieldMask: []string{"description"}, + wantCheckFuncs: []checkFunc{ + checkVersion(2), // Version remains same even though row is updated + checkUpdateCatalogRequestCurrentDescriptionNil(), + checkUpdateCatalogRequestNewDescriptionNil(), + checkDescription(""), + checkSecrets(map[string]interface{}{ + "one": "two", + }), + checkNumUpdated(1), + checkVerifyCatalogOplog(oplog.OpType_OP_TYPE_UPDATE), + }, + }, + { + name: "update attributes (add)", + changeFuncs: []changeHostCatalogFunc{changeAttributes(map[string]interface{}{ + "baz": "qux", + })}, + version: 2, + fieldMask: []string{"attributes"}, + wantCheckFuncs: []checkFunc{ + checkVersion(3), + checkUpdateCatalogRequestCurrentAttributes(map[string]interface{}{ + "foo": "bar", + }), + checkUpdateCatalogRequestNewAttributes(map[string]interface{}{ + "foo": "bar", + "baz": "qux", + }), + checkAttributes(map[string]interface{}{ + "foo": "bar", + "baz": "qux", + }), + checkSecrets(map[string]interface{}{ + "one": "two", + }), + checkNumUpdated(1), + checkVerifyCatalogOplog(oplog.OpType_OP_TYPE_UPDATE), + }, + }, + { + name: "update attributes (overwrite)", + changeFuncs: []changeHostCatalogFunc{changeAttributes(map[string]interface{}{ + "foo": "baz", + })}, + version: 2, + fieldMask: []string{"attributes"}, + wantCheckFuncs: []checkFunc{ + checkVersion(3), + checkUpdateCatalogRequestCurrentAttributes(map[string]interface{}{ + "foo": "bar", + }), + checkUpdateCatalogRequestNewAttributes(map[string]interface{}{ + "foo": "baz", + }), + checkAttributes(map[string]interface{}{ + "foo": "baz", + }), + checkSecrets(map[string]interface{}{ + "one": "two", + }), + checkNumUpdated(1), + checkVerifyCatalogOplog(oplog.OpType_OP_TYPE_UPDATE), + }, + }, + { + name: "update attributes (null)", + changeFuncs: []changeHostCatalogFunc{changeAttributes(map[string]interface{}{ + "foo": nil, + })}, + version: 2, + fieldMask: []string{"attributes"}, + wantCheckFuncs: []checkFunc{ + checkVersion(3), + checkUpdateCatalogRequestCurrentAttributes(map[string]interface{}{ + "foo": "bar", + }), + checkUpdateCatalogRequestNewAttributes(map[string]interface{}{}), + checkAttributes(map[string]interface{}{}), + checkSecrets(map[string]interface{}{ + "one": "two", + }), + checkNumUpdated(1), + checkVerifyCatalogOplog(oplog.OpType_OP_TYPE_UPDATE), + }, + }, + { + name: "update attributes (combined)", + changeFuncs: []changeHostCatalogFunc{changeAttributes(map[string]interface{}{ + "a": "b", + "foo": "baz", + })}, + version: 2, + fieldMask: []string{"attributes.a", "attributes.foo"}, + wantCheckFuncs: []checkFunc{ + checkVersion(3), + checkUpdateCatalogRequestCurrentAttributes(map[string]interface{}{ + "foo": "bar", + }), + checkUpdateCatalogRequestNewAttributes(map[string]interface{}{ + "a": "b", + "foo": "baz", + }), + checkAttributes(map[string]interface{}{ + "a": "b", + "foo": "baz", + }), + checkSecrets(map[string]interface{}{ + "one": "two", + }), + checkNumUpdated(1), + checkVerifyCatalogOplog(oplog.OpType_OP_TYPE_UPDATE), + }, + }, + { + name: "update secrets", + changeFuncs: []changeHostCatalogFunc{changeSecrets(map[string]interface{}{ + "three": "four", + "five": "six", + })}, + version: 2, + fieldMask: []string{"secrets.three", "secrets.five"}, + wantCheckFuncs: []checkFunc{ + checkVersion(3), + checkUpdateCatalogRequestPersistedSecrets(map[string]interface{}{ + "one": "two", + }), + checkUpdateCatalogRequestSecrets(map[string]interface{}{ + "three": "four", + "five": "six", + }), + checkSecrets(map[string]interface{}{ + "three": "four", + "five": "six", + }), + checkNumUpdated(1), + checkVerifyCatalogOplog(oplog.OpType_OP_TYPE_UPDATE), + }, + }, + { + name: "update secrets, return nil secrets from plugin", + withRespSecretsNil: true, + changeFuncs: []changeHostCatalogFunc{changeSecrets(map[string]interface{}{ + "three": "four", + })}, + version: 2, + fieldMask: []string{"secrets.three"}, + wantCheckFuncs: []checkFunc{ + checkVersion(2), // no-op + checkUpdateCatalogRequestPersistedSecrets(map[string]interface{}{ + "one": "two", + }), + checkUpdateCatalogRequestSecrets(map[string]interface{}{ + "three": "four", + }), + checkSecrets(map[string]interface{}{ + "one": "two", + }), + checkNumUpdated(1), + }, + }, + { + name: "delete secrets", + changeFuncs: []changeHostCatalogFunc{changeSecrets(map[string]interface{}{})}, + version: 2, + fieldMask: []string{"secrets"}, + wantCheckFuncs: []checkFunc{ + checkVersion(3), + checkUpdateCatalogRequestPersistedSecrets(map[string]interface{}{ + "one": "two", + }), + checkUpdateCatalogRequestSecrets(map[string]interface{}{}), + checkSecretsDeleted(), + checkNumUpdated(1), + checkVerifyCatalogOplog(oplog.OpType_OP_TYPE_UPDATE), + }, + }, + { + name: "update name and secrets", + changeFuncs: []changeHostCatalogFunc{ + changeName("foo"), + changeSecrets(map[string]interface{}{ + "three": "four", + }), + }, + version: 2, + fieldMask: []string{"name", "secrets.three"}, + wantCheckFuncs: []checkFunc{ + checkVersion(3), + checkUpdateCatalogRequestCurrentNameNil(), + checkUpdateCatalogRequestNewName("foo"), + checkUpdateCatalogRequestSecrets(map[string]interface{}{ + "three": "four", + }), + checkName("foo"), + checkSecrets(map[string]interface{}{ + "three": "four", + }), + checkNumUpdated(1), + checkVerifyCatalogOplog(oplog.OpType_OP_TYPE_UPDATE), + }, + }, + } + + // Finally define a function for bringing the test subject catalog. + // This function also returns a function to clean up the catalog + // afterwards. + setupHostCatalog := func(t *testing.T, ctx context.Context) *HostCatalog { + t.Helper() + require := require.New(t) + + cat := TestCatalog(t, dbConn, projectScope.PublicId, testPlugin.GetPublicId()) + // Set some (default) attributes on our test catalog + cat.Attributes = mustMarshal(map[string]interface{}{ + "foo": "bar", + }) + + numCatUpdated, err := dbRW.Update(ctx, cat, []string{"attributes"}, []string{}) + require.NoError(err) + require.Equal(1, numCatUpdated) + + // Set up some secrets + cSecretProto := mustStruct(map[string]interface{}{ + "one": "two", + }) + cSecret, err := newHostCatalogSecret(ctx, cat.GetPublicId(), cSecretProto) + require.NoError(err) + scopeWrapper, err := dbKmsCache.GetWrapper(ctx, cat.GetScopeId(), kms.KeyPurposeDatabase) + require.NoError(err) + require.NoError(cSecret.encrypt(ctx, scopeWrapper)) + cSecretQ, cSecretV := cSecret.upsertQuery() + secretsUpdated, err := dbRW.Exec(ctx, cSecretQ, cSecretV) + require.NoError(err) + require.Equal(1, secretsUpdated) + + t.Cleanup(func() { + t.Helper() + assert := assert.New(t) + n, err := dbRW.Delete(ctx, cat) + assert.NoError(err) + assert.Equal(1, n) + }) + + return cat + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + origCat := setupHostCatalog(t, ctx) + + pluginMap := testPluginMap + if tt.withEmptyPluginMap { + pluginMap = make(map[string]plgpb.HostPluginServiceClient) + } + pluginError = tt.withPluginError + t.Cleanup(func() { pluginError = nil }) + repo, err := NewRepository(dbRW, dbRW, dbKmsCache, sched, pluginMap) + require.NoError(err) + require.NotNil(repo) + + workingCat := origCat.clone() + for _, cf := range tt.changeFuncs { + workingCat = cf(workingCat) + } + + setRespSecretsNil = tt.withRespSecretsNil + var gotPlugin *hostplugin.Plugin + gotCatalog, gotPlugin, gotNumUpdated, err = repo.UpdateCatalog(ctx, workingCat, tt.version, tt.fieldMask) + if tt.wantIsErr != 0 { + require.Equal(db.NoRowsAffected, gotNumUpdated) + require.Truef(errors.Match(errors.T(tt.wantIsErr), err), "want err: %q got: %q", tt.wantIsErr, err) + return + } + require.NoError(err) + t.Cleanup(func() { gotOnUpdateCatalogRequest = nil }) + + // Quick assertion that the catalog is not nil and that the plugin ID in + // the catalog and the plugin ID in the returned plugin match. Use assert + // as that's what our checks use in the table test's defined checks. + assert.NotNil(gotCatalog) + assert.NotNil(gotPlugin) + assert.Equal(gotCatalog.PluginId, gotPlugin.PublicId) + // Perform checks + for _, check := range tt.wantCheckFuncs { + check(t, ctx) + } + }) + } +} + +func TestRepository_LookupCatalog(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + sched := scheduler.TestScheduler(t, conn, wrapper) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + plg := hostplg.TestPlugin(t, conn, "test") + plgm := map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): &WrappingPluginClient{Server: &TestPluginServer{}}, + } + cat := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + badId, err := newHostCatalogId(ctx) + assert.NoError(t, err) + assert.NotNil(t, badId) + + tests := []struct { + name string + id string + want *HostCatalog + wantErr errors.Code + }{ + { + name: "found", + id: cat.GetPublicId(), + want: cat, + }, + { + name: "not-found", + id: badId, + want: nil, + }, + { + name: "bad-public-id", + id: "", + want: nil, + wantErr: errors.InvalidParameter, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + kms := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms, sched, plgm) + assert.NoError(err) + assert.NotNil(repo) + + got, _, err := repo.LookupCatalog(ctx, tt.id) + if tt.wantErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err) + return + } + assert.NoError(err) + + switch { + case tt.want == nil: + assert.Nil(got) + case tt.want != nil: + assert.NotNil(got) + assert.Equal(got, tt.want) + } + }) + } +} + +func TestRepository_ListCatalogs_Multiple_Scopes(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + rw := db.New(conn) + kms := kms.TestKms(t, conn, wrapper) + sched := scheduler.TestScheduler(t, conn, wrapper) + plg := hostplg.TestPlugin(t, conn, "test") + plgm := map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): &WrappingPluginClient{Server: &TestPluginServer{}}, + } + repo, err := NewRepository(rw, rw, kms, sched, plgm) + assert.NoError(t, err) + assert.NotNil(t, repo) + + const numPerScope = 10 + var projs []string + var total int + for i := 0; i < numPerScope; i++ { + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + projs = append(projs, prj.GetPublicId()) + for j := 0; j < numPerScope; j++ { + TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + total++ + } + } + + got, plgs, err := repo.ListCatalogs(context.Background(), projs) + require.NoError(t, err) + assert.Equal(t, total, len(got)) + assert.Contains(t, plgs, plg) +} + +func TestRepository_DeleteCatalog(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + sched := scheduler.TestScheduler(t, conn, wrapper) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + plg := hostplg.TestPlugin(t, conn, "test") + pluginInstance := &TestPluginServer{} + plgm := map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): &WrappingPluginClient{Server: pluginInstance}, + } + cat := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + cat2 := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + badId, err := newHostCatalogId(ctx) + assert.NoError(t, err) + assert.NotNil(t, badId) + + kms := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms, sched, plgm) + assert.NoError(t, err) + assert.NotNil(t, repo) + + tests := []struct { + name string + id string + pluginChecker func(*testing.T, *plgpb.OnDeleteCatalogRequest) error + want int + wantErr errors.Code + }{ + { + name: "found", + id: cat.GetPublicId(), + pluginChecker: func(t *testing.T, req *plgpb.OnDeleteCatalogRequest) error { + assert.Equal(t, cat.GetPublicId(), req.GetCatalog().GetId()) + return nil + }, + want: 1, + }, + { + name: "ignore error", + id: cat2.GetPublicId(), + pluginChecker: func(t *testing.T, req *plgpb.OnDeleteCatalogRequest) error { + assert.Equal(t, cat2.GetPublicId(), req.GetCatalog().GetId()) + return fmt.Errorf("This is a test error") + }, + want: 1, + }, + { + name: "not-found", + id: badId, + pluginChecker: func(t *testing.T, req *plgpb.OnDeleteCatalogRequest) error { + assert.Fail(t, "Should not call the plugin when catalog isn't found") + return nil + }, + want: 0, + }, + { + name: "bad-public-id", + id: "", + pluginChecker: func(t *testing.T, req *plgpb.OnDeleteCatalogRequest) error { + assert.Fail(t, "Should not call the plugin for a bad id") + return nil + }, + want: 0, + wantErr: errors.InvalidParameter, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + pluginInstance.OnDeleteCatalogFn = func(_ context.Context, request *plgpb.OnDeleteCatalogRequest) (*plgpb.OnDeleteCatalogResponse, error) { + return nil, tt.pluginChecker(t, request) + } + got, err := repo.DeleteCatalog(context.Background(), tt.id) + if tt.wantErr != 0 { + assert.Truef(t, errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err) + return + } + assert.NoError(t, err) + assert.Equal(t, tt.want, got, "row count") + }) + } +} + +func TestRepository_DeleteCatalogX(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + sched := scheduler.TestScheduler(t, conn, wrapper) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + plg := hostplg.TestPlugin(t, conn, "test") + plgm := map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): &WrappingPluginClient{Server: &TestPluginServer{}}, + } + cat := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + badId, err := newHostCatalogId(ctx) + assert.NoError(t, err) + assert.NotNil(t, badId) + + tests := []struct { + name string + id string + want *HostCatalog + wantErr errors.Code + }{ + { + name: "found", + id: cat.GetPublicId(), + want: cat, + }, + { + name: "not-found", + id: badId, + want: nil, + }, + { + name: "bad-public-id", + id: "", + want: nil, + wantErr: errors.InvalidParameter, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + kms := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms, sched, plgm) + assert.NoError(err) + assert.NotNil(repo) + + got, _, err := repo.LookupCatalog(ctx, tt.id) + if tt.wantErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err) + return + } + assert.NoError(err) + + switch { + case tt.want == nil: + assert.Nil(got) + case tt.want != nil: + assert.NotNil(got) + assert.Equal(got, tt.want) + } + }) + } +} + +func assertPluginBasedPublicId(t *testing.T, prefix, actual string) { + t.Helper() + assert.NotEmpty(t, actual) + parts := strings.Split(actual, "_") + assert.Equalf(t, 2, len(parts), "want 1 '_' in PublicId, got %d in %q", len(parts), actual) + assert.Equalf(t, prefix, parts[0], "PublicId want prefix: %q, got: %q in %q", prefix, parts[0], actual) +} + +// mustStruct creates a structpb.Struct, and panics if there is an +// error. +func mustStruct(in map[string]interface{}) *structpb.Struct { + out, err := structpb.NewStruct(in) + if err != nil { + panic(err) + } + + return out +} + +// mustMarshal behaves like mustStruct but also converts the Struct +// to wire-format data. +func mustMarshal(in map[string]interface{}) []byte { + b, err := proto.Marshal(mustStruct(in)) + if err != nil { + panic(err) + } + + return b +} diff --git a/internal/host/plugin/repository_host_set.go b/internal/host/plugin/repository_host_set.go new file mode 100644 index 000000000..109f8755e --- /dev/null +++ b/internal/host/plugin/repository_host_set.go @@ -0,0 +1,822 @@ +package plugin + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/db/timestamp" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/host" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/libs/endpoint" + "github.com/hashicorp/boundary/internal/libs/patchstruct" + "github.com/hashicorp/boundary/internal/oplog" + hostplugin "github.com/hashicorp/boundary/internal/plugin/host" + pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +// CreateSet inserts s into the repository and returns a new HostSet +// containing the host set's PublicId. s is not changed. s must contain a +// valid CatalogId. s must not contain a PublicId. The PublicId is +// generated and assigned by this method. opt is ignored. +// +// Both s.Name and s.Description are optional. If s.Name is set, it must be +// unique within s.CatalogId. +func (r *Repository) CreateSet(ctx context.Context, scopeId string, s *HostSet, _ ...Option) (*HostSet, *hostplugin.Plugin, error) { + const op = "plugin.(Repository).CreateSet" + if s == nil { + return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "nil HostSet") + } + if s.HostSet == nil { + return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "nil embedded HostSet") + } + if s.CatalogId == "" { + return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "no catalog id") + } + if s.PublicId != "" { + return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "public id not empty") + } + if scopeId == "" { + return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "no scope id") + } + if s.SyncIntervalSeconds < -1 { + return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "invalid sync interval") + } + if s.Attributes == nil { + return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "nil attributes") + } + s = s.clone() + + // Use PatchBytes' functionality that does not add keys where the values + // are nil to the resulting struct since we do not want to store nil valued + // attributes. + var err error + s.Attributes, err = patchstruct.PatchBytes([]byte{}, s.Attributes) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + + c, per, err := r.getCatalog(ctx, s.CatalogId) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("looking up catalog")) + } + id, err := newHostSetId(ctx) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + s.PublicId = id + s.LastSyncTime = timestamp.New(time.Unix(0, 0)) + s.NeedSync = true + + plgClient, ok := r.plugins[c.GetPluginId()] + if !ok || plgClient == nil { + return nil, nil, errors.New(ctx, errors.Internal, op, fmt.Sprintf("plugin %q not available", c.GetPluginId())) + } + + plgHc, err := toPluginCatalog(ctx, c) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + plgHs, err := toPluginSet(ctx, s) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + + var preferredEndpoints []interface{} + if s.PreferredEndpoints != nil { + preferredEndpoints = make([]interface{}, 0, len(s.PreferredEndpoints)) + for i, e := range s.PreferredEndpoints { + obj, err := host.NewPreferredEndpoint(ctx, s.PublicId, uint32(i+1), e) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + preferredEndpoints = append(preferredEndpoints, obj) + } + } + + oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) + } + + var calledPluginSuccessfully bool + + var returnedHostSet *HostSet + _, err = r.writer.DoTx( + ctx, + db.StdRetryCnt, + db.ExpBackoff{}, + func(_ db.Reader, w db.Writer) error { + msgs := make([]*oplog.Message, 0, len(preferredEndpoints)+2) + ticket, err := w.GetTicket(s) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket")) + } + + returnedHostSet = s.clone() + + var hsOplogMsg oplog.Message + if err := w.Create(ctx, returnedHostSet, db.NewOplogMsg(&hsOplogMsg)); err != nil { + return errors.Wrap(ctx, err, op) + } + msgs = append(msgs, &hsOplogMsg) + + if len(preferredEndpoints) > 0 { + peOplogMsgs := make([]*oplog.Message, 0, len(preferredEndpoints)) + if err := w.CreateItems(ctx, preferredEndpoints, db.NewOplogMsgs(&peOplogMsgs)); err != nil { + return err + } + msgs = append(msgs, peOplogMsgs...) + } + + if !calledPluginSuccessfully { + if _, err := plgClient.OnCreateSet(ctx, &plgpb.OnCreateSetRequest{Catalog: plgHc, Set: plgHs, Persisted: per}); err != nil { + if status.Code(err) != codes.Unimplemented { + return errors.Wrap(ctx, err, op) + } + } + calledPluginSuccessfully = true + } + + metadata := s.oplog(oplog.OpType_OP_TYPE_CREATE) + if err := w.WriteOplogEntryWith(ctx, oplogWrapper, ticket, metadata, msgs); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog")) + } + + return nil + }, + ) + + if err != nil { + if errors.IsUniqueError(err) { + return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in catalog: %s: name %s already exists", s.CatalogId, s.Name))) + } + return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in catalog: %s", s.CatalogId))) + } + + // The set now exists in the plugin, sync it immediately. + _ = r.scheduler.UpdateJobNextRunInAtLeast(ctx, setSyncJobName, 0) + + plg, err := r.getPlugin(ctx, c.GetPluginId()) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + return returnedHostSet, plg, nil +} + +// UpdateSet updates the repository for host set entry s with the +// values populated, for the fields listed in fieldMask. It returns a +// new HostSet containing the updated values, the hosts in the set, +// and a count of the number of records updated. s is not changed. +// +// s must contain a valid PublicId and CatalogId. Name, Description, +// Attributes, and PreferredEndpoints can be updated. Name must be +// unique among all sets associated with a single catalog. +// +// An attribute of s will be set to NULL in the database if the +// attribute in s is the zero value and it is included in fieldMask. +// Note that this does not apply to s.Attributes - a null +// s.Attributes is a no-op for modifications. Rather, if fields need +// to be reset, its field in c.Attributes should individually set to +// null. +// +// Updates are sent to OnUpdateSet with a full copy of both the +// current set, the state of the new set should it be updated, along +// with its parent host catalog and persisted state (can include +// secrets). This is a stateless call and does not affect the final +// record written, but some plugins may perform some actions on this +// call. Update of the record in the database is aborted if this call +// fails. +func (r *Repository) UpdateSet(ctx context.Context, scopeId string, s *HostSet, version uint32, fieldMask []string, opt ...Option) (*HostSet, []*Host, *hostplugin.Plugin, int, error) { + const op = "plugin.(Repository).UpdateSet" + if s == nil { + return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "nil HostSet") + } + if s.HostSet == nil { + return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "nil embedded HostSet") + } + if s.PublicId == "" { + return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "no public id") + } + if scopeId == "" { + return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "no scope id") + } + if len(fieldMask) == 0 { + return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.EmptyFieldMask, op, "empty field mask") + } + + // Get the old set first. We patch the record first before + // sending it to the DB for updating so that we can run on + // OnUpdateSet. Note that the field masks are still used for + // updating. + sets, plg, err := r.getSets(ctx, s.PublicId, "") + if err != nil { + return nil, nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op) + } + + if len(sets) == 0 { + return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.RecordNotFound, op, fmt.Sprintf("host set id %q not found", s.PublicId)) + } + + if len(sets) != 1 { + return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.Internal, op, fmt.Sprintf("unexpected amount of sets found, want=1, got=%d", len(sets))) + } + + currentSet := sets[0] + + // Assert the version of the current set to make sure we're + // updating the correct one. + if currentSet.GetVersion() != version { + return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.VersionMismatch, op, fmt.Sprintf("set version mismatch, want=%d, got=%d", currentSet.GetVersion(), version)) + } + + // Clone the set so that we can set fields. + newSet := currentSet.clone() + var updateAttributes bool + var dbMask, nullFields []string + const ( + endpointOpNoop = "endpointOpNoop" + endpointOpDelete = "endpointOpDelete" + endpointOpUpdate = "endpointOpUpdate" + ) + var endpointOp string = endpointOpNoop + for _, f := range fieldMask { + switch { + case strings.EqualFold("name", f) && s.Name == "": + nullFields = append(nullFields, "name") + newSet.Name = s.Name + case strings.EqualFold("name", f) && s.Name != "": + dbMask = append(dbMask, "name") + newSet.Name = s.Name + case strings.EqualFold("description", f) && s.Description == "": + nullFields = append(nullFields, "description") + newSet.Description = s.Description + case strings.EqualFold("description", f) && s.Description != "": + dbMask = append(dbMask, "description") + newSet.Description = s.Description + case strings.EqualFold("PreferredEndpoints", f) && len(s.PreferredEndpoints) == 0: + endpointOp = endpointOpDelete + newSet.PreferredEndpoints = s.PreferredEndpoints + case strings.EqualFold("PreferredEndpoints", f) && len(s.PreferredEndpoints) != 0: + endpointOp = endpointOpUpdate + newSet.PreferredEndpoints = s.PreferredEndpoints + case strings.EqualFold("attributes", strings.Split(f, ".")[0]): + // Flag attributes for updating. While multiple masks may be + // sent, we only need to do this once. + updateAttributes = true + + default: + return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidFieldMask, op, fmt.Sprintf("invalid field mask: %s", f)) + } + } + + if updateAttributes { + dbMask = append(dbMask, "attributes") + if s.Attributes != nil { + newSet.Attributes, err = patchstruct.PatchBytes(newSet.Attributes, s.Attributes) + if err != nil { + return nil, nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("error in set attribute JSON")) + } + } else { + newSet.Attributes = make([]byte, 0) + } + + // Flag the record as needing a sync since we've updated + // attributes. + dbMask = append(dbMask, "NeedSync") + newSet.NeedSync = true + } + + // Get the host catalog for the set and its persisted data. + catalog, persisted, err := r.getCatalog(ctx, newSet.CatalogId) + if err != nil { + return nil, nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error looking up catalog with id %q", newSet.CatalogId))) + } + + // Assert that the catalog scope ID and supplied scope ID match. + if catalog.ScopeId != scopeId { + return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("catalog id %q not in scope id %q", newSet.CatalogId, scopeId)) + } + + // Get the plugin client. + plgClient, ok := r.plugins[catalog.GetPluginId()] + if !ok || plgClient == nil { + return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.Internal, op, fmt.Sprintf("plugin %q not available", catalog.GetPluginId())) + } + + // Convert the catalog values to API protobuf values, which is what + // we use for the plugin hook calls. + plgHc, err := toPluginCatalog(ctx, catalog) + if err != nil { + return nil, nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op) + } + currentPlgSet, err := toPluginSet(ctx, currentSet) + if err != nil { + return nil, nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op) + } + newPlgSet, err := toPluginSet(ctx, newSet) + if err != nil { + return nil, nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op) + } + + // Get the preferred endpoints to write out. + var preferredEndpoints []interface{} + if endpointOp == endpointOpUpdate { + preferredEndpoints = make([]interface{}, 0, len(newSet.PreferredEndpoints)) + for i, e := range newSet.PreferredEndpoints { + obj, err := host.NewPreferredEndpoint(ctx, newSet.PublicId, uint32(i+1), e) + if err != nil { + return nil, nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op) + } + preferredEndpoints = append(preferredEndpoints, obj) + } + } + + oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog) + if err != nil { + return nil, nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) + } + + // If the call to the plugin succeeded, we do not want to call it again if + // the transaction failed and is being retried. + var pluginCalledSuccessfully bool + + var setUpdated, preferredEndpointsUpdated bool + var returnedSet *HostSet + var hosts []*Host + _, err = r.writer.DoTx( + ctx, + db.StdRetryCnt, + db.ExpBackoff{}, + func(reader db.Reader, w db.Writer) error { + returnedSet = newSet.clone() + msgs := make([]*oplog.Message, 0, len(preferredEndpoints)+len(currentSet.PreferredEndpoints)+2) + ticket, err := w.GetTicket(s) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket")) + } + + if len(dbMask) != 0 || len(nullFields) != 0 { + var hsOplogMsg oplog.Message + numUpdated, err := w.Update( + ctx, + returnedSet, + dbMask, + nullFields, + db.NewOplogMsg(&hsOplogMsg), + db.WithVersion(&version), + ) + if err != nil { + return errors.Wrap(ctx, err, op) + } + + if numUpdated != 1 { + return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("expected 1 set to be updated, got %d", numUpdated)) + } + + setUpdated = true + msgs = append(msgs, &hsOplogMsg) + } + + switch endpointOp { + case endpointOpDelete: + // Delete all old endpoint entries. + var peps []interface{} + for i := 1; i <= len(currentSet.PreferredEndpoints); i++ { + p := host.AllocPreferredEndpoint() + p.HostSetId, p.Priority = currentSet.GetPublicId(), uint32(i) + peps = append(peps, p) + } + deleteOplogMsgs := make([]*oplog.Message, 0, len(peps)) + _, err := w.DeleteItems(ctx, peps, db.NewOplogMsgs(&deleteOplogMsgs)) + if err != nil { + return errors.Wrap(ctx, err, op) + } + + // Only append the oplog message if an operation was actually + // performed. + if len(deleteOplogMsgs) > 0 { + preferredEndpointsUpdated = true + msgs = append(msgs, deleteOplogMsgs...) + } + + case endpointOpUpdate: + if len(currentSet.PreferredEndpoints) > 0 { + // Delete all old endpoint entries. + var peps []interface{} + for i := 1; i <= len(currentSet.PreferredEndpoints); i++ { + p := host.AllocPreferredEndpoint() + p.HostSetId, p.Priority = currentSet.GetPublicId(), uint32(i) + peps = append(peps, p) + } + deleteOplogMsgs := make([]*oplog.Message, 0, len(peps)) + _, err := w.DeleteItems(ctx, peps, db.WithDebug(true), db.NewOplogMsgs(&deleteOplogMsgs)) + if err != nil { + return errors.Wrap(ctx, err, op) + } + + // Only append the oplog message if an operation was actually + // performed. + if len(deleteOplogMsgs) > 0 { + msgs = append(msgs, deleteOplogMsgs...) + } + } + + // Create the new entries. + peCreateOplogMsgs := make([]*oplog.Message, 0, len(preferredEndpoints)) + if err := w.CreateItems(ctx, preferredEndpoints, db.NewOplogMsgs(&peCreateOplogMsgs)); err != nil { + return err + } + + preferredEndpointsUpdated = true + msgs = append(msgs, peCreateOplogMsgs...) + } + + if !setUpdated && preferredEndpointsUpdated { + returnedSet.Version = uint32(version) + 1 + var hsOplogMsg oplog.Message + numUpdated, err := w.Update( + ctx, + returnedSet, + []string{"version"}, + []string{}, + db.NewOplogMsg(&hsOplogMsg), + db.WithVersion(&version), + ) + if err != nil { + return errors.Wrap(ctx, err, op) + } + + if numUpdated != 1 { + return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("expected 1 set to be updated, got %d", numUpdated)) + } + + msgs = append(msgs, &hsOplogMsg) + } + + if len(msgs) != 0 { + metadata := s.oplog(oplog.OpType_OP_TYPE_UPDATE) + if err := w.WriteOplogEntryWith(ctx, oplogWrapper, ticket, metadata, msgs); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog")) + } + } + + hsAgg := &hostSetAgg{PublicId: currentSet.GetPublicId()} + if err := reader.LookupByPublicId(ctx, hsAgg); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("looking up host after update")) + } + returnedSet, err = hsAgg.toHostSet(ctx) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("converting from host aggregate to host after update")) + } + + if !pluginCalledSuccessfully { + _, err = plgClient.OnUpdateSet(ctx, &plgpb.OnUpdateSetRequest{ + CurrentSet: currentPlgSet, + NewSet: newPlgSet, + Catalog: plgHc, + Persisted: persisted, + }) + if err != nil { + if status.Code(err) != codes.Unimplemented { + return errors.Wrap(ctx, err, op) + } + } + } + + return nil + }, + ) + + if err != nil { + if errors.IsUniqueError(err) { + return nil, nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in %s: name %s already exists", newSet.PublicId, newSet.Name))) + } + return nil, nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in %s", newSet.PublicId))) + } + + hosts, err = listHostBySetIds(ctx, r.reader, []string{returnedSet.PublicId}, opt...) + if err != nil { + return nil, nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op) + } + + var numUpdated int + if setUpdated || preferredEndpointsUpdated { + numUpdated = 1 + } + + if updateAttributes { + // Request a host sync since we have updated attributes. + _ = r.scheduler.UpdateJobNextRunInAtLeast(ctx, setSyncJobName, 0) + } + + return returnedSet, hosts, plg, numUpdated, nil +} + +// LookupSet will look up a host set in the repository and return the host set, +// as well as host IDs that match. If the host set is not found, it will return +// nil, nil, nil. No options are currently supported. +func (r *Repository) LookupSet(ctx context.Context, publicId string, _ ...host.Option) (*HostSet, *hostplugin.Plugin, error) { + const op = "plugin.(Repository).LookupSet" + if publicId == "" { + return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "no public id") + } + + sets, plg, err := r.getSets(ctx, publicId, "") + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + + switch { + case len(sets) == 0: + return nil, nil, nil // not an error to return no rows for a "lookup" + case len(sets) > 1: + return nil, nil, errors.New(ctx, errors.NotSpecificIntegrity, op, fmt.Sprintf("%s matched more than 1 ", publicId)) + } + + return sets[0], plg, nil +} + +// ListSets returns a slice of HostSets for the catalogId. WithLimit is the +// only option supported. +func (r *Repository) ListSets(ctx context.Context, catalogId string, opt ...host.Option) ([]*HostSet, *hostplugin.Plugin, error) { + const op = "plugin.(Repository).ListSets" + if catalogId == "" { + return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing catalog id") + } + + sets, plg, err := r.getSets(ctx, "", catalogId, opt...) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + return sets, plg, nil +} + +// DeleteSet deletes the host set for the provided id from the repository +// returning a count of the number of records deleted. All options are +// ignored. +func (r *Repository) DeleteSet(ctx context.Context, scopeId string, publicId string, _ ...Option) (int, error) { + const op = "plugin.(Repository).DeleteSet" + if publicId == "" { + return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "no public id") + } + if scopeId == "" { + return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "no scope id") + } + + sets, plg, err := r.getSets(ctx, publicId, "") + if err != nil { + return db.NoRowsAffected, errors.Wrap(ctx, err, op) + } + if len(sets) != 1 { + return db.NoRowsAffected, nil + } + s := sets[0] + + c, p, err := r.getCatalog(ctx, s.GetCatalogId()) + if err != nil && errors.IsNotFoundError(err) { + return db.NoRowsAffected, errors.Wrap(ctx, err, op) + } + if c == nil { + return db.NoRowsAffected, nil + } + + plgClient, ok := r.plugins[plg.GetPublicId()] + if !ok || plgClient == nil { + return 0, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("plugin %q not available", c.GetPluginId())) + } + plgHc, err := toPluginCatalog(ctx, c) + if err != nil { + return 0, errors.Wrap(ctx, err, op) + } + plgHs, err := toPluginSet(ctx, s) + if err != nil { + return 0, errors.Wrap(ctx, err, op) + } + _, err = plgClient.OnDeleteSet(ctx, &plgpb.OnDeleteSetRequest{Catalog: plgHc, Persisted: p, Set: plgHs}) + if err != nil { + // Even if the plugin returns an error, we ignore it and proceed + // with deleting the set. + } + + oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog) + if err != nil { + return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) + } + + var rowsDeleted int + _, err = r.writer.DoTx( + ctx, db.StdRetryCnt, db.ExpBackoff{}, + func(_ db.Reader, w db.Writer) (err error) { + ds := s.clone() + rowsDeleted, err = w.Delete(ctx, ds, db.WithOplog(oplogWrapper, s.oplog(oplog.OpType_OP_TYPE_DELETE))) + if err != nil { + return errors.Wrap(ctx, err, op) + } + if rowsDeleted > 1 { + return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted") + } + return nil + }, + ) + + if err != nil { + return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("delete failed for %s", s.PublicId))) + } + + return rowsDeleted, nil +} + +func (r *Repository) getSets(ctx context.Context, publicId string, catalogId string, opt ...host.Option) ([]*HostSet, *hostplugin.Plugin, error) { + const op = "plugin.(Repository).getSets" + if publicId == "" && catalogId == "" { + return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing search criteria: both host set id and catalog id are empty") + } + if publicId != "" && catalogId != "" { + return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "searching for both a host set id and a catalog id is not supported") + } + + opts, err := host.GetOpts(opt...) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + + limit := r.defaultLimit + if opts.WithLimit != 0 { + // non-zero signals an override of the default limit for the repo. + limit = opts.WithLimit + } + + args := make([]interface{}, 0, 1) + var where string + + switch { + case publicId != "": + where, args = "public_id = ?", append(args, publicId) + default: + where, args = "catalog_id = ?", append(args, catalogId) + } + + dbArgs := []db.Option{db.WithLimit(limit)} + + if opts.WithOrderByCreateTime { + if opts.Ascending { + dbArgs = append(dbArgs, db.WithOrder("create_time asc")) + } else { + dbArgs = append(dbArgs, db.WithOrder("create_time")) + } + } + + var aggHostSets []*hostSetAgg + if err := r.reader.SearchWhere(ctx, &aggHostSets, where, args, dbArgs...); err != nil { + return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in %s", publicId))) + } + + if len(aggHostSets) == 0 { + return nil, nil, nil + } + plgId := aggHostSets[0].PluginId + + sets := make([]*HostSet, 0, len(aggHostSets)) + for _, agg := range aggHostSets { + hs, err := agg.toHostSet(ctx) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + sets = append(sets, hs) + } + var plg *hostplugin.Plugin + if plgId != "" { + plg, err = r.getPlugin(ctx, plgId) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + } + + return sets, plg, nil +} + +// toPluginSet returns a host set in the format expected by the host plugin system. +func toPluginSet(ctx context.Context, in *HostSet) (*pb.HostSet, error) { + const op = "plugin.toPluginSet" + if in == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "nil storage plugin") + } + + var name, description *wrapperspb.StringValue + if inName := in.GetName(); inName != "" { + name = wrapperspb.String(inName) + } + if inDescription := in.GetDescription(); inDescription != "" { + description = wrapperspb.String(inDescription) + } + + hs := &pb.HostSet{ + Id: in.GetPublicId(), + Name: name, + Description: description, + PreferredEndpoints: in.PreferredEndpoints, + } + if in.GetAttributes() != nil { + attrs := &structpb.Struct{} + if err := proto.Unmarshal(in.GetAttributes(), attrs); err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to marshal attributes")) + } + hs.Attributes = attrs + } + return hs, nil +} + +// Endpoints provides all the endpoints available for a given set id. +// An error is returned if the set, related catalog, or related plugin are +// unable to be retrieved. If a host does not contain an addressible endpoint +// it is not included in the resulting slice of endpoints. +func (r *Repository) Endpoints(ctx context.Context, setIds []string) ([]*host.Endpoint, error) { + const op = "plugin.(Repository).Endpoints" + if len(setIds) == 0 { + return nil, errors.New(ctx, errors.InvalidParameter, op, "no set ids") + } + + // Fist, look up the sets corresponding to the set IDs + var setAggs []*hostSetAgg + if err := r.reader.SearchWhere(ctx, &setAggs, "public_id in (?)", []interface{}{setIds}); err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("can't retrieve sets %v", setIds))) + } + if len(setAggs) == 0 { + return nil, nil + } + setIdToSet := make(map[string]*HostSet, len(setAggs)) + for _, s := range setAggs { + var err error + setIdToSet[s.PublicId], err = s.toHostSet(ctx) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + } + + var setMembers []*HostSetMember + if err := r.reader.SearchWhere(ctx, &setMembers, "set_id in (?)", []interface{}{setIds}); err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("can't retrieve set members for sets %v", setIds))) + } + if len(setMembers) == 0 { + return nil, nil + } + + hostIdToSetIds := make(map[string][]string) + for _, m := range setMembers { + hostIdToSetIds[m.GetHostId()] = append(hostIdToSetIds[m.GetHostId()], m.GetSetId()) + } + var hostIds []string + for hid := range hostIdToSetIds { + hostIds = append(hostIds, hid) + } + var hostAggs []*hostAgg + if err := r.reader.SearchWhere(ctx, &hostAggs, "public_id in (?)", []interface{}{hostIds}); err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("can't retrieve hosts %v", hostIds))) + } + if len(hostAggs) == 0 { + return nil, nil + } + + var es []*host.Endpoint + for _, ha := range hostAggs { + h := ha.toHost() + for _, sId := range hostIdToSetIds[h.GetPublicId()] { + s := setIdToSet[sId] + pref, err := endpoint.NewPreferencer(ctx, endpoint.WithPreferenceOrder(s.PreferredEndpoints)) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("getting preferencer for set %q", sId))) + } + var opts []endpoint.Option + if len(h.GetIpAddresses()) > 0 { + opts = append(opts, endpoint.WithIpAddrs(h.GetIpAddresses())) + } + if len(h.GetDnsNames()) > 0 { + opts = append(opts, endpoint.WithIpAddrs(h.GetDnsNames())) + } + addr, err := pref.Choose(ctx, opts...) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + if addr == "" { + continue + } + es = append(es, &host.Endpoint{ + HostId: h.GetPublicId(), + SetId: sId, + Address: addr, + }) + } + } + + return es, nil +} diff --git a/internal/host/plugin/repository_host_set_test.go b/internal/host/plugin/repository_host_set_test.go new file mode 100644 index 000000000..5bda4438b --- /dev/null +++ b/internal/host/plugin/repository_host_set_test.go @@ -0,0 +1,1631 @@ +package plugin + +import ( + "context" + "fmt" + "sort" + "strings" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/db/timestamp" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/host" + "github.com/hashicorp/boundary/internal/host/plugin/store" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/oplog" + hostplg "github.com/hashicorp/boundary/internal/plugin/host" + "github.com/hashicorp/boundary/internal/scheduler" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +func TestRepository_CreateSet(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + sched := scheduler.TestScheduler(t, conn, wrapper) + _, prj := iam.TestScopes(t, iamRepo) + plg := hostplg.TestPlugin(t, conn, "create") + unimplementedPlugin := hostplg.TestPlugin(t, conn, "unimplemented") + + catalog := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + unimplementedPluginCatalog := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + attrs := []byte{} + + tests := []struct { + name string + in *HostSet + opts []Option + want *HostSet + wantPluginCalled bool + wantIsErr errors.Code + }{ + { + name: "nil-HostSet", + wantIsErr: errors.InvalidParameter, + }, + { + name: "nil-embedded-HostSet", + in: &HostSet{}, + wantIsErr: errors.InvalidParameter, + }, + { + name: "invalid-no-catalog-id", + in: &HostSet{ + HostSet: &store.HostSet{ + Attributes: attrs, + }, + }, + wantIsErr: errors.InvalidParameter, + }, + { + name: "invalid-public-id-set", + in: &HostSet{ + HostSet: &store.HostSet{ + CatalogId: catalog.PublicId, + PublicId: "abcd_OOOOOOOOOO", + Attributes: attrs, + }, + }, + wantIsErr: errors.InvalidParameter, + }, + { + name: "invalid-no-attribte", + in: &HostSet{ + HostSet: &store.HostSet{ + CatalogId: catalog.PublicId, + }, + }, + wantIsErr: errors.InvalidParameter, + }, + { + name: "invalid-sync-interval-too-negative", + in: &HostSet{ + HostSet: &store.HostSet{ + CatalogId: catalog.PublicId, + SyncIntervalSeconds: -99, + Attributes: attrs, + }, + }, + wantIsErr: errors.InvalidParameter, + }, + { + name: "valid-no-options", + in: &HostSet{ + HostSet: &store.HostSet{ + CatalogId: catalog.PublicId, + Attributes: attrs, + }, + }, + want: &HostSet{ + HostSet: &store.HostSet{ + CatalogId: catalog.PublicId, + Attributes: attrs, + }, + }, + wantPluginCalled: true, + }, + { + name: "valid-preferred-endpoints", + in: &HostSet{ + HostSet: &store.HostSet{ + CatalogId: catalog.PublicId, + Attributes: attrs, + }, + PreferredEndpoints: []string{"cidr:1.2.3.4/32", "dns:a.b.c"}, + }, + want: &HostSet{ + HostSet: &store.HostSet{ + CatalogId: catalog.PublicId, + Attributes: attrs, + }, + PreferredEndpoints: []string{"cidr:1.2.3.4/32", "dns:a.b.c"}, + }, + wantPluginCalled: true, + }, + { + name: "valid-with-name", + in: &HostSet{ + HostSet: &store.HostSet{ + CatalogId: catalog.PublicId, + Name: "test-name-repo", + Attributes: attrs, + }, + }, + want: &HostSet{ + HostSet: &store.HostSet{ + CatalogId: catalog.PublicId, + Name: "test-name-repo", + Attributes: attrs, + }, + }, + wantPluginCalled: true, + }, + { + name: "valid-sync-interval-disabled", + in: &HostSet{ + HostSet: &store.HostSet{ + CatalogId: catalog.PublicId, + SyncIntervalSeconds: -1, + Attributes: attrs, + }, + }, + want: &HostSet{ + HostSet: &store.HostSet{ + CatalogId: catalog.PublicId, + SyncIntervalSeconds: -1, + Attributes: attrs, + }, + }, + wantPluginCalled: true, + }, + { + name: "valid-sync-interval-positive", + in: &HostSet{ + HostSet: &store.HostSet{ + CatalogId: catalog.PublicId, + SyncIntervalSeconds: 60, + Attributes: attrs, + }, + }, + want: &HostSet{ + HostSet: &store.HostSet{ + CatalogId: catalog.PublicId, + SyncIntervalSeconds: 60, + Attributes: attrs, + }, + }, + wantPluginCalled: true, + }, + { + name: "valid-unimplemented-plugin", + in: &HostSet{ + HostSet: &store.HostSet{ + CatalogId: unimplementedPluginCatalog.PublicId, + Name: "valid-unimplemented-plugin", + Attributes: attrs, + }, + }, + want: &HostSet{ + HostSet: &store.HostSet{ + CatalogId: unimplementedPluginCatalog.PublicId, + Name: "valid-unimplemented-plugin", + Attributes: attrs, + }, + }, + wantPluginCalled: true, + }, + { + name: "valid-with-description", + in: &HostSet{ + HostSet: &store.HostSet{ + CatalogId: catalog.PublicId, + Description: ("test-description-repo"), + Attributes: attrs, + }, + }, + want: &HostSet{ + HostSet: &store.HostSet{ + CatalogId: catalog.PublicId, + Description: ("test-description-repo"), + Attributes: attrs, + }, + }, + wantPluginCalled: true, + }, + { + name: "valid-with-custom-attributes", + in: &HostSet{ + HostSet: &store.HostSet{ + CatalogId: catalog.PublicId, + Description: ("test-description-repo"), + Attributes: func() []byte { + st, err := structpb.NewStruct(map[string]interface{}{ + "k1": "foo", + "removed": nil, + }) + require.NoError(t, err) + b, err := proto.Marshal(st) + require.NoError(t, err) + return b + }(), + }, + }, + want: &HostSet{ + HostSet: &store.HostSet{ + CatalogId: catalog.PublicId, + Description: ("test-description-repo"), + Attributes: func() []byte { + b, err := proto.Marshal(&structpb.Struct{Fields: map[string]*structpb.Value{"k1": structpb.NewStringValue("foo")}}) + require.NoError(t, err) + return b + }(), + }, + }, + wantPluginCalled: true, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + var pluginReceivedAttrs *structpb.Struct + var pluginCalled bool + plgm := map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): NewWrappingPluginClient(TestPluginServer{OnCreateSetFn: func(ctx context.Context, req *plgpb.OnCreateSetRequest) (*plgpb.OnCreateSetResponse, error) { + pluginCalled = true + pluginReceivedAttrs = req.GetSet().GetAttributes() + return &plgpb.OnCreateSetResponse{}, nil + }}), + unimplementedPlugin.GetPublicId(): NewWrappingPluginClient(TestPluginServer{OnCreateSetFn: func(ctx context.Context, req *plgpb.OnCreateSetRequest) (*plgpb.OnCreateSetResponse, error) { + pluginCalled = true + pluginReceivedAttrs = req.GetSet().GetAttributes() + return plgpb.UnimplementedHostPluginServiceServer{}.OnCreateSet(ctx, req) + }}), + } + repo, err := NewRepository(rw, rw, kms, sched, plgm) + require.NoError(err) + require.NotNil(repo) + got, plgInfo, err := repo.CreateSet(context.Background(), prj.GetPublicId(), tt.in, tt.opts...) + assert.Equal(tt.wantPluginCalled, pluginCalled) + if tt.wantIsErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "want err: %q got: %q", tt.wantIsErr, err) + assert.Nil(got) + return + } + require.NoError(err) + assert.Empty(tt.in.PublicId) + require.NotNil(got) + assert.True(strings.HasPrefix(got.GetPublicId(), HostSetPrefix)) + assert.NotSame(tt.in, got) + assert.Equal(tt.want.Name, got.GetName()) + assert.Equal(tt.want.Description, got.GetDescription()) + assert.Equal(got.GetCreateTime(), got.GetUpdateTime()) + wantedPluginAttributes := &structpb.Struct{} + require.NoError(proto.Unmarshal(tt.want.Attributes, wantedPluginAttributes)) + assert.Empty(cmp.Diff(wantedPluginAttributes, pluginReceivedAttrs, protocmp.Transform())) + assert.Empty(cmp.Diff(plgInfo, plg, protocmp.Transform())) + + assert.NoError(db.TestVerifyOplog(t, rw, got.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))) + }) + } + + t.Run("invalid-duplicate-names", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + var pluginCalled bool + plgm := map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): NewWrappingPluginClient(TestPluginServer{OnCreateSetFn: func(ctx context.Context, req *plgpb.OnCreateSetRequest) (*plgpb.OnCreateSetResponse, error) { + pluginCalled = true + return &plgpb.OnCreateSetResponse{}, nil + }}), + } + repo, err := NewRepository(rw, rw, kms, sched, plgm) + require.NoError(err) + require.NotNil(repo) + + _, prj := iam.TestScopes(t, iamRepo) + catalog := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + + in := &HostSet{ + HostSet: &store.HostSet{ + CatalogId: catalog.PublicId, + Name: "test-name-repo", + Attributes: []byte{}, + }, + } + + got, _, err := repo.CreateSet(context.Background(), prj.GetPublicId(), in) + require.NoError(err) + require.NotNil(got) + assert.True(pluginCalled) + assert.True(strings.HasPrefix(got.GetPublicId(), HostSetPrefix)) + assert.NotSame(in, got) + assert.Equal(in.Name, got.GetName()) + assert.Equal(in.Description, got.GetDescription()) + assert.Equal(got.GetCreateTime(), got.GetUpdateTime()) + + // reset pluginCalled so we can see the duplicate name causes the plugin + // to not get called. + pluginCalled = false + got2, _, err := repo.CreateSet(context.Background(), prj.GetPublicId(), in) + assert.False(pluginCalled) + assert.Truef(errors.Match(errors.T(errors.NotUnique), err), "want err code: %v got err: %v", errors.NotUnique, err) + assert.Nil(got2) + }) + + t.Run("valid-duplicate-names-diff-catalogs", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + var pluginCalled bool + plgm := map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): NewWrappingPluginClient(TestPluginServer{OnCreateSetFn: func(ctx context.Context, req *plgpb.OnCreateSetRequest) (*plgpb.OnCreateSetResponse, error) { + pluginCalled = true + return &plgpb.OnCreateSetResponse{}, nil + }}), + } + repo, err := NewRepository(rw, rw, kms, sched, plgm) + require.NoError(err) + require.NotNil(repo) + + _, prj := iam.TestScopes(t, iamRepo) + catalogA := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + catalogB := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + + in := &HostSet{ + HostSet: &store.HostSet{ + Name: "test-name-repo", + Attributes: []byte{}, + }, + } + in2 := in.clone() + + in.CatalogId = catalogA.PublicId + // reset pluginCalled so we can see that the plugin is called during this + // createSet call + pluginCalled = false + got, _, err := repo.CreateSet(context.Background(), prj.GetPublicId(), in) + assert.True(pluginCalled) + require.NoError(err) + require.NotNil(got) + assert.True(strings.HasPrefix(got.GetPublicId(), HostSetPrefix)) + assert.NotSame(in, got) + assert.Equal(in.Name, got.GetName()) + assert.Equal(in.Description, got.GetDescription()) + assert.Equal(got.GetCreateTime(), got.GetUpdateTime()) + + in2.CatalogId = catalogB.PublicId + // reset pluginCalled so we can see that the plugin is called during this + // createSet call + pluginCalled = false + got2, _, err := repo.CreateSet(context.Background(), prj.GetPublicId(), in2) + require.NoError(err) + require.NotNil(got2) + assert.True(pluginCalled) + assert.True(strings.HasPrefix(got.GetPublicId(), HostSetPrefix)) + assert.NotSame(in2, got2) + assert.Equal(in2.Name, got2.GetName()) + assert.Equal(in2.Description, got2.GetDescription()) + assert.Equal(got2.GetCreateTime(), got2.GetUpdateTime()) + }) +} + +func TestRepository_UpdateSet(t *testing.T) { + ctx := context.Background() + dbConn, _ := db.TestSetup(t, "postgres") + dbRW := db.New(dbConn) + dbWrapper := db.TestWrapper(t) + sched := scheduler.TestScheduler(t, dbConn, dbWrapper) + dbKmsCache := kms.TestKms(t, dbConn, dbWrapper) + _, projectScope := iam.TestScopes(t, iam.TestRepo(t, dbConn, dbWrapper)) + + testPlugin := hostplg.TestPlugin(t, dbConn, "test") + dummyPluginMap := map[string]plgpb.HostPluginServiceClient{ + testPlugin.GetPublicId(): &WrappingPluginClient{Server: &plgpb.UnimplementedHostPluginServiceServer{}}, + } + + // Set up a test catalog and the secrets for it + testCatalog := TestCatalog(t, dbConn, projectScope.PublicId, testPlugin.GetPublicId()) + testCatalogSecret, err := newHostCatalogSecret(ctx, testCatalog.GetPublicId(), mustStruct(map[string]interface{}{ + "one": "two", + })) + require.NoError(t, err) + scopeWrapper, err := dbKmsCache.GetWrapper(ctx, testCatalog.GetScopeId(), kms.KeyPurposeDatabase) + require.NoError(t, err) + require.NoError(t, testCatalogSecret.encrypt(ctx, scopeWrapper)) + testCatalogSecretQ, testCatalogSecretV := testCatalogSecret.upsertQuery() + secretsUpdated, err := dbRW.Exec(ctx, testCatalogSecretQ, testCatalogSecretV) + require.NoError(t, err) + require.Equal(t, 1, secretsUpdated) + + // Create a test duplicate set. We don't use this set, it just + // exists to ensure that we can test for conflicts when setting + // the name. + const testDuplicateSetName = "duplicate-set-name" + testDuplicateSet := TestSet(t, dbConn, dbKmsCache, sched, testCatalog, dummyPluginMap) + testDuplicateSet.Name = testDuplicateSetName + setsUpdated, err := dbRW.Update(ctx, testDuplicateSet, []string{"name"}, []string{}) + require.NoError(t, err) + require.Equal(t, 1, setsUpdated) + + // Define some helpers here to make the test table more readable. + type changeHostSetFunc func(s *HostSet) *HostSet + + changeSetToNil := func() changeHostSetFunc { + return func(_ *HostSet) *HostSet { + return nil + } + } + + changeEmbeddedSetToNil := func() changeHostSetFunc { + return func(s *HostSet) *HostSet { + s.HostSet = nil + return s + } + } + + changePublicId := func(v string) changeHostSetFunc { + return func(s *HostSet) *HostSet { + s.PublicId = v + return s + } + } + + changeName := func(v string) changeHostSetFunc { + return func(s *HostSet) *HostSet { + s.Name = v + return s + } + } + + changeDescription := func(s string) changeHostSetFunc { + return func(c *HostSet) *HostSet { + c.Description = s + return c + } + } + + changePreferredEndpoints := func(s []string) changeHostSetFunc { + return func(c *HostSet) *HostSet { + c.PreferredEndpoints = s + return c + } + } + + changeAttributes := func(m map[string]interface{}) changeHostSetFunc { + return func(c *HostSet) *HostSet { + c.Attributes = mustMarshal(m) + return c + } + } + + changeAttributesNil := func() changeHostSetFunc { + return func(c *HostSet) *HostSet { + c.Attributes = nil + return c + } + } + + // Define some checks that will be used in the below tests. Some of + // these are re-used, so we define them here. Most of these are + // assertions and no particular one is non-fatal in that they will + // stop execution. Note that these are executed after wantIsErr, so + // if that is set in an individual table test, these will not be + // executed. + // + // Note that we define some state here, similar to how we + // previously defined gotOnUpdateCatalogRequest above next the + // plugin map. + type checkHostSetFunc func(t *testing.T, got *HostSet) + + checkVersion := func(want uint32) checkHostSetFunc { + return func(t *testing.T, got *HostSet) { + t.Helper() + assert.Equal(t, want, got.Version, "checkVersion") + } + } + + checkName := func(want string) checkHostSetFunc { + return func(t *testing.T, got *HostSet) { + t.Helper() + assert.Equal(t, want, got.Name, "checkName") + } + } + + checkDescription := func(want string) checkHostSetFunc { + return func(t *testing.T, got *HostSet) { + t.Helper() + assert.Equal(t, want, got.Description, "checkDescription") + } + } + + checkPreferredEndpoints := func(want []string) checkHostSetFunc { + return func(t *testing.T, got *HostSet) { + t.Helper() + assert.Equal(t, want, got.PreferredEndpoints, "checkPreferredEndpoints") + } + } + + checkAttributes := func(want map[string]interface{}) checkHostSetFunc { + return func(t *testing.T, got *HostSet) { + t.Helper() + st := &structpb.Struct{} + require.NoError(t, proto.Unmarshal(got.Attributes, st)) + assert.Empty(t, cmp.Diff(mustStruct(want), st, protocmp.Transform())) + } + } + + checkNeedSync := func(want bool) checkHostSetFunc { + return func(t *testing.T, got *HostSet) { + t.Helper() + assert.Equal(t, want, got.NeedSync, "checkNeedSync") + } + } + + checkVerifySetOplog := func(op oplog.OpType) checkHostSetFunc { + return func(t *testing.T, got *HostSet) { + t.Helper() + assert.NoError(t, + db.TestVerifyOplog( + t, + dbRW, + got.PublicId, + db.WithOperation(op), + db.WithCreateNotBefore(10*time.Second), + ), + ) + } + } + + type checkPluginReqFunc func(t *testing.T, got *plgpb.OnUpdateSetRequest) + + checkUpdateSetRequestCurrentNameNil := func() checkPluginReqFunc { + return func(t *testing.T, got *plgpb.OnUpdateSetRequest) { + t.Helper() + assert.Nil(t, got.CurrentSet.Name, "checkUpdateSetRequestCurrentNameNil") + } + } + + checkUpdateSetRequestNewName := func(want string) checkPluginReqFunc { + return func(t *testing.T, got *plgpb.OnUpdateSetRequest) { + t.Helper() + assert.Equal(t, wrapperspb.String(want), got.NewSet.Name, "checkUpdateSetRequestNewName") + } + } + + checkUpdateSetRequestNewNameNil := func() checkPluginReqFunc { + return func(t *testing.T, got *plgpb.OnUpdateSetRequest) { + t.Helper() + assert.Nil(t, got.NewSet.Name, "checkUpdateSetRequestNewNameNil") + } + } + + checkUpdateSetRequestCurrentDescriptionNil := func() checkPluginReqFunc { + return func(t *testing.T, got *plgpb.OnUpdateSetRequest) { + t.Helper() + assert.Nil(t, got.CurrentSet.Description, "checkUpdateSetRequestCurrentDescriptionNil") + } + } + + checkUpdateSetRequestNewDescription := func(want string) checkPluginReqFunc { + return func(t *testing.T, got *plgpb.OnUpdateSetRequest) { + t.Helper() + assert.Equal(t, wrapperspb.String(want), got.NewSet.Description, "checkUpdateSetRequestNewDescription") + } + } + + checkUpdateSetRequestNewDescriptionNil := func() checkPluginReqFunc { + return func(t *testing.T, got *plgpb.OnUpdateSetRequest) { + t.Helper() + assert.Nil(t, got.NewSet.Description, "checkUpdateSetRequestNewDescriptionNil") + } + } + + checkUpdateSetRequestCurrentPreferredEndpoints := func(want []string) checkPluginReqFunc { + return func(t *testing.T, got *plgpb.OnUpdateSetRequest) { + t.Helper() + assert.Equal(t, want, got.CurrentSet.PreferredEndpoints, "checkUpdateSetRequestCurrentPreferredEndpoints") + } + } + + checkUpdateSetRequestNewPreferredEndpoints := func(want []string) checkPluginReqFunc { + return func(t *testing.T, got *plgpb.OnUpdateSetRequest) { + t.Helper() + assert.Equal(t, want, got.NewSet.PreferredEndpoints, "checkUpdateSetRequestNewPreferredEndpoints") + } + } + + checkUpdateSetRequestNewPreferredEndpointsNil := func() checkPluginReqFunc { + return func(t *testing.T, got *plgpb.OnUpdateSetRequest) { + t.Helper() + assert.Nil(t, got.NewSet.PreferredEndpoints, "checkUpdateSetRequestNewPreferredEndpointsNil") + } + } + + checkUpdateSetRequestCurrentAttributes := func(want map[string]interface{}) checkPluginReqFunc { + return func(t *testing.T, got *plgpb.OnUpdateSetRequest) { + t.Helper() + assert.Empty(t, cmp.Diff(mustStruct(want), got.CurrentSet.Attributes, protocmp.Transform()), "checkUpdateSetRequestCurrentAttributes") + } + } + + checkUpdateSetRequestNewAttributes := func(want map[string]interface{}) checkPluginReqFunc { + return func(t *testing.T, got *plgpb.OnUpdateSetRequest) { + t.Helper() + assert.Empty(t, cmp.Diff(mustStruct(want), got.NewSet.Attributes, protocmp.Transform()), "checkUpdateSetRequestNewAttributes") + } + } + + checkUpdateSetRequestPersistedSecrets := func(want map[string]interface{}) checkPluginReqFunc { + return func(t *testing.T, got *plgpb.OnUpdateSetRequest) { + t.Helper() + assert.Empty(t, cmp.Diff(mustStruct(want), got.Persisted.Secrets, protocmp.Transform()), "checkUpdateSetRequestPersistedSecrets") + } + } + + // Finally define a function for bringing the test subject host + // set. + setupBareHostSet := func(t *testing.T, ctx context.Context) (*HostSet, []*Host) { + t.Helper() + set := TestSet( + t, + dbConn, + dbKmsCache, + sched, + testCatalog, + dummyPluginMap, + ) + + t.Cleanup(func() { + t.Helper() + assert := assert.New(t) + n, err := dbRW.Delete(ctx, set) + assert.NoError(err) + assert.Equal(1, n) + }) + + return set, nil + } + + // Create a host that will be verified as belonging to the created set below. + testHostExternIdPrefix := "test-host-external-id" + testHosts := make([]*Host, 3) + for i := 0; i <= 2; i++ { + testHosts[i] = TestHost(t, dbConn, testCatalog.PublicId, fmt.Sprintf("%s-%d", testHostExternIdPrefix, i)) + } + + // Finally define a function for bringing the test subject host + // set. + setupHostSet := func(t *testing.T, ctx context.Context) (*HostSet, []*Host) { + t.Helper() + require := require.New(t) + + set := TestSet( + t, + dbConn, + dbKmsCache, + sched, + testCatalog, + dummyPluginMap, + WithPreferredEndpoints([]string{"cidr:192.168.0.0/24", "cidr:192.168.1.0/24", "cidr:172.16.0.0/12"}), + ) + // Set some (default) attributes on our test set + set.Attributes = mustMarshal(map[string]interface{}{ + "foo": "bar", + }) + + // Set some fake sync detail to the set. + set.LastSyncTime = timestamp.New(time.Now()) + set.NeedSync = false + + numSetsUpdated, err := dbRW.Update(ctx, set, []string{"attributes", "LastSyncTime", "NeedSync"}, []string{}) + require.NoError(err) + require.Equal(1, numSetsUpdated) + + // Add some hosts to the host set. + TestSetMembers(t, dbConn, set.PublicId, testHosts) + + t.Cleanup(func() { + t.Helper() + assert := assert.New(t) + n, err := dbRW.Delete(ctx, set) + assert.NoError(err) + assert.Equal(1, n) + }) + + return set, testHosts + } + + tests := []struct { + name string + startingSet func(*testing.T, context.Context) (*HostSet, []*Host) + withScopeId *string + withEmptyPluginMap bool + withPluginError error + changeFuncs []changeHostSetFunc + version uint32 + fieldMask []string + wantCheckSetFuncs []checkHostSetFunc + wantCheckPluginReqFuncs []checkPluginReqFunc + wantIsErr errors.Code + }{ + { + name: "nil set", + startingSet: setupBareHostSet, + changeFuncs: []changeHostSetFunc{changeSetToNil()}, + wantIsErr: errors.InvalidParameter, + }, + { + name: "nil embedded set", + startingSet: setupBareHostSet, + changeFuncs: []changeHostSetFunc{changeEmbeddedSetToNil()}, + wantIsErr: errors.InvalidParameter, + }, + { + name: "missing public id", + startingSet: setupBareHostSet, + changeFuncs: []changeHostSetFunc{changePublicId("")}, + wantIsErr: errors.InvalidParameter, + }, + { + name: "missing scope id", + startingSet: setupBareHostSet, + withScopeId: func() *string { a := ""; return &a }(), + wantIsErr: errors.InvalidParameter, + }, + { + name: "empty field mask", + startingSet: setupBareHostSet, + fieldMask: nil, // Should be testing on len + wantIsErr: errors.EmptyFieldMask, + }, + { + name: "bad set id", + startingSet: setupBareHostSet, + changeFuncs: []changeHostSetFunc{changePublicId("badid")}, + fieldMask: []string{"name"}, + wantIsErr: errors.RecordNotFound, + }, + { + name: "version mismatch", + startingSet: setupBareHostSet, + changeFuncs: []changeHostSetFunc{changeName("foo")}, + version: 4, + fieldMask: []string{"name"}, + wantIsErr: errors.VersionMismatch, + }, + { + name: "mismatched scope id to catalog scope", + startingSet: setupBareHostSet, + withScopeId: func() *string { a := "badid"; return &a }(), + fieldMask: []string{"name"}, + wantIsErr: errors.InvalidParameter, + }, + { + name: "plugin lookup error", + startingSet: setupBareHostSet, + withEmptyPluginMap: true, + fieldMask: []string{"name"}, + wantIsErr: errors.Internal, + }, + { + name: "plugin invocation error", + startingSet: setupBareHostSet, + withPluginError: errors.New(context.Background(), errors.Internal, "TestRepository_UpdateSet/plugin_invocation_error", "test plugin error"), + fieldMask: []string{"name"}, + wantIsErr: errors.Internal, + }, + { + name: "update name (duplicate)", + startingSet: setupBareHostSet, + changeFuncs: []changeHostSetFunc{changeName(testDuplicateSetName)}, + fieldMask: []string{"name"}, + wantIsErr: errors.NotUnique, + }, + { + name: "update name", + startingSet: setupHostSet, + changeFuncs: []changeHostSetFunc{changeName("foo")}, + fieldMask: []string{"name"}, + wantCheckPluginReqFuncs: []checkPluginReqFunc{ + checkUpdateSetRequestCurrentNameNil(), + checkUpdateSetRequestNewName("foo"), + checkUpdateSetRequestPersistedSecrets(map[string]interface{}{ + "one": "two", + }), + }, + wantCheckSetFuncs: []checkHostSetFunc{ + checkVersion(3), + checkName("foo"), + checkNeedSync(false), + checkVerifySetOplog(oplog.OpType_OP_TYPE_UPDATE), + }, + }, + { + name: "update name to same", + startingSet: setupHostSet, + changeFuncs: []changeHostSetFunc{changeName("")}, + fieldMask: []string{"name"}, + wantCheckPluginReqFuncs: []checkPluginReqFunc{ + checkUpdateSetRequestCurrentNameNil(), + checkUpdateSetRequestNewNameNil(), + checkUpdateSetRequestPersistedSecrets(map[string]interface{}{ + "one": "two", + }), + }, + wantCheckSetFuncs: []checkHostSetFunc{ + checkVersion(2), // Version remains same even though row is updated + checkName(""), + checkNeedSync(false), + checkVerifySetOplog(oplog.OpType_OP_TYPE_UPDATE), + }, + }, + { + name: "update description", + startingSet: setupHostSet, + changeFuncs: []changeHostSetFunc{changeDescription("foo")}, + fieldMask: []string{"description"}, + wantCheckPluginReqFuncs: []checkPluginReqFunc{ + checkUpdateSetRequestCurrentDescriptionNil(), + checkUpdateSetRequestNewDescription("foo"), + checkUpdateSetRequestPersistedSecrets(map[string]interface{}{ + "one": "two", + }), + }, + wantCheckSetFuncs: []checkHostSetFunc{ + checkVersion(3), + checkDescription("foo"), + checkNeedSync(false), + checkVerifySetOplog(oplog.OpType_OP_TYPE_UPDATE), + }, + }, + { + name: "update description to same", + startingSet: setupHostSet, + changeFuncs: []changeHostSetFunc{changeDescription("")}, + fieldMask: []string{"description"}, + wantCheckPluginReqFuncs: []checkPluginReqFunc{ + checkUpdateSetRequestCurrentDescriptionNil(), + checkUpdateSetRequestNewDescriptionNil(), + checkUpdateSetRequestPersistedSecrets(map[string]interface{}{ + "one": "two", + }), + }, + wantCheckSetFuncs: []checkHostSetFunc{ + checkVersion(2), // Version remains same even though row is updated + checkDescription(""), + checkNeedSync(false), + checkVerifySetOplog(oplog.OpType_OP_TYPE_UPDATE), + }, + }, + { + name: "add preferred endpoints", + startingSet: setupBareHostSet, + changeFuncs: []changeHostSetFunc{changePreferredEndpoints([]string{"cidr:10.0.0.0/24"})}, + fieldMask: []string{"PreferredEndpoints"}, + wantCheckPluginReqFuncs: []checkPluginReqFunc{ + checkUpdateSetRequestCurrentPreferredEndpoints(nil), + checkUpdateSetRequestNewPreferredEndpoints([]string{"cidr:10.0.0.0/24"}), + checkUpdateSetRequestPersistedSecrets(map[string]interface{}{ + "one": "two", + }), + }, + wantCheckSetFuncs: []checkHostSetFunc{ + checkVersion(2), + checkPreferredEndpoints([]string{"cidr:10.0.0.0/24"}), + checkNeedSync(true), + checkVerifySetOplog(oplog.OpType_OP_TYPE_UPDATE), + }, + }, + { + name: "update preferred endpoints", + startingSet: setupHostSet, + changeFuncs: []changeHostSetFunc{changePreferredEndpoints([]string{"cidr:10.0.0.0/24"})}, + fieldMask: []string{"PreferredEndpoints"}, + wantCheckPluginReqFuncs: []checkPluginReqFunc{ + checkUpdateSetRequestCurrentPreferredEndpoints([]string{"cidr:192.168.0.0/24", "cidr:192.168.1.0/24", "cidr:172.16.0.0/12"}), + checkUpdateSetRequestNewPreferredEndpoints([]string{"cidr:10.0.0.0/24"}), + checkUpdateSetRequestPersistedSecrets(map[string]interface{}{ + "one": "two", + }), + }, + wantCheckSetFuncs: []checkHostSetFunc{ + checkVersion(3), + checkPreferredEndpoints([]string{"cidr:10.0.0.0/24"}), + checkNeedSync(false), + checkVerifySetOplog(oplog.OpType_OP_TYPE_UPDATE), + }, + }, + { + name: "clear preferred endpoints", + startingSet: setupHostSet, + changeFuncs: []changeHostSetFunc{changePreferredEndpoints(nil)}, + fieldMask: []string{"PreferredEndpoints"}, + wantCheckPluginReqFuncs: []checkPluginReqFunc{ + checkUpdateSetRequestCurrentPreferredEndpoints([]string{"cidr:192.168.0.0/24", "cidr:192.168.1.0/24", "cidr:172.16.0.0/12"}), + checkUpdateSetRequestNewPreferredEndpointsNil(), + checkUpdateSetRequestPersistedSecrets(map[string]interface{}{ + "one": "two", + }), + }, + wantCheckSetFuncs: []checkHostSetFunc{ + checkVersion(3), + checkPreferredEndpoints(nil), + checkNeedSync(false), + }, + }, + { + name: "update attributes (add)", + startingSet: setupHostSet, + changeFuncs: []changeHostSetFunc{changeAttributes(map[string]interface{}{ + "baz": "qux", + })}, + fieldMask: []string{"attributes"}, + wantCheckPluginReqFuncs: []checkPluginReqFunc{ + checkUpdateSetRequestCurrentAttributes(map[string]interface{}{ + "foo": "bar", + }), + checkUpdateSetRequestNewAttributes(map[string]interface{}{ + "foo": "bar", + "baz": "qux", + }), + checkUpdateSetRequestPersistedSecrets(map[string]interface{}{ + "one": "two", + }), + }, + wantCheckSetFuncs: []checkHostSetFunc{ + checkVersion(3), + checkAttributes(map[string]interface{}{ + "foo": "bar", + "baz": "qux", + }), + checkNeedSync(true), + checkVerifySetOplog(oplog.OpType_OP_TYPE_UPDATE), + }, + }, + { + name: "update attributes (overwrite)", + startingSet: setupHostSet, + changeFuncs: []changeHostSetFunc{changeAttributes(map[string]interface{}{ + "foo": "baz", + })}, + fieldMask: []string{"attributes"}, + wantCheckPluginReqFuncs: []checkPluginReqFunc{ + checkUpdateSetRequestCurrentAttributes(map[string]interface{}{ + "foo": "bar", + }), + checkUpdateSetRequestNewAttributes(map[string]interface{}{ + "foo": "baz", + }), + checkUpdateSetRequestPersistedSecrets(map[string]interface{}{ + "one": "two", + }), + }, + wantCheckSetFuncs: []checkHostSetFunc{ + checkVersion(3), + checkAttributes(map[string]interface{}{ + "foo": "baz", + }), + checkNeedSync(true), + checkVerifySetOplog(oplog.OpType_OP_TYPE_UPDATE), + }, + }, + { + name: "update attributes (null)", + startingSet: setupHostSet, + changeFuncs: []changeHostSetFunc{changeAttributes(map[string]interface{}{ + "foo": nil, + })}, + fieldMask: []string{"attributes"}, + wantCheckPluginReqFuncs: []checkPluginReqFunc{ + checkUpdateSetRequestCurrentAttributes(map[string]interface{}{ + "foo": "bar", + }), + checkUpdateSetRequestNewAttributes(map[string]interface{}{}), + checkUpdateSetRequestPersistedSecrets(map[string]interface{}{ + "one": "two", + }), + }, + wantCheckSetFuncs: []checkHostSetFunc{ + checkVersion(3), + checkAttributes(map[string]interface{}{}), + checkNeedSync(true), + checkVerifySetOplog(oplog.OpType_OP_TYPE_UPDATE), + }, + }, + { + name: "update attributes (full null)", + startingSet: setupHostSet, + changeFuncs: []changeHostSetFunc{changeAttributesNil()}, + fieldMask: []string{"attributes"}, + wantCheckPluginReqFuncs: []checkPluginReqFunc{ + checkUpdateSetRequestCurrentAttributes(map[string]interface{}{ + "foo": "bar", + }), + checkUpdateSetRequestNewAttributes(map[string]interface{}{}), + checkUpdateSetRequestPersistedSecrets(map[string]interface{}{ + "one": "two", + }), + }, + wantCheckSetFuncs: []checkHostSetFunc{ + checkVersion(3), + checkAttributes(map[string]interface{}{}), + checkNeedSync(true), + checkVerifySetOplog(oplog.OpType_OP_TYPE_UPDATE), + }, + }, + { + name: "update attributes (combined)", + startingSet: setupHostSet, + changeFuncs: []changeHostSetFunc{changeAttributes(map[string]interface{}{ + "a": "b", + "foo": "baz", + })}, + fieldMask: []string{"attributes.a", "attributes.foo"}, + wantCheckPluginReqFuncs: []checkPluginReqFunc{ + checkUpdateSetRequestCurrentAttributes(map[string]interface{}{ + "foo": "bar", + }), + checkUpdateSetRequestNewAttributes(map[string]interface{}{ + "a": "b", + "foo": "baz", + }), + checkUpdateSetRequestPersistedSecrets(map[string]interface{}{ + "one": "two", + }), + }, + wantCheckSetFuncs: []checkHostSetFunc{ + checkVersion(3), + checkAttributes(map[string]interface{}{ + "a": "b", + "foo": "baz", + }), + checkNeedSync(true), + checkVerifySetOplog(oplog.OpType_OP_TYPE_UPDATE), + }, + }, + { + name: "update name and preferred endpoints", + startingSet: setupHostSet, + changeFuncs: []changeHostSetFunc{ + changePreferredEndpoints([]string{"cidr:10.0.0.0/24"}), + changeName("foo"), + }, + fieldMask: []string{"name", "PreferredEndpoints"}, + wantCheckPluginReqFuncs: []checkPluginReqFunc{ + checkUpdateSetRequestCurrentNameNil(), + checkUpdateSetRequestNewName("foo"), + checkUpdateSetRequestCurrentPreferredEndpoints([]string{"cidr:192.168.0.0/24", "cidr:192.168.1.0/24", "cidr:172.16.0.0/12"}), + checkUpdateSetRequestNewPreferredEndpoints([]string{"cidr:10.0.0.0/24"}), + checkUpdateSetRequestPersistedSecrets(map[string]interface{}{ + "one": "two", + }), + }, + wantCheckSetFuncs: []checkHostSetFunc{ + checkVersion(3), + checkName("foo"), + checkPreferredEndpoints([]string{"cidr:10.0.0.0/24"}), + checkNeedSync(false), + checkVerifySetOplog(oplog.OpType_OP_TYPE_UPDATE), + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + + // Define a plugin "manager", basically just a map with a mock + // plugin in it. This also includes functionality to capture the + // state, and set an error and the returned secrets to nil. Note + // that the way that this is set up means that the tests cannot run + // in parallel, but there could be other factors affecting that as + // well. + var gotOnUpdateCallCount int + testPluginMap := map[string]plgpb.HostPluginServiceClient{ + testPlugin.GetPublicId(): &WrappingPluginClient{ + Server: &TestPluginServer{ + OnUpdateSetFn: func(_ context.Context, req *plgpb.OnUpdateSetRequest) (*plgpb.OnUpdateSetResponse, error) { + gotOnUpdateCallCount++ + for _, check := range tt.wantCheckPluginReqFuncs { + check(t, req) + } + return &plgpb.OnUpdateSetResponse{}, tt.withPluginError + }, + }, + }, + } + + origSet, wantedHosts := tt.startingSet(t, ctx) + + pluginMap := testPluginMap + if tt.withEmptyPluginMap { + pluginMap = make(map[string]plgpb.HostPluginServiceClient) + } + repo, err := NewRepository(dbRW, dbRW, dbKmsCache, sched, pluginMap) + require.NoError(err) + require.NotNil(repo) + + workingSet := origSet.clone() + for _, cf := range tt.changeFuncs { + workingSet = cf(workingSet) + } + + scopeId := testCatalog.ScopeId + if tt.withScopeId != nil { + scopeId = *tt.withScopeId + } + + version := origSet.Version + if tt.version != 0 { + version = tt.version + } + + gotUpdatedSet, gotHosts, gotPlugin, gotNumUpdated, err := repo.UpdateSet(ctx, scopeId, workingSet, version, tt.fieldMask) + t.Cleanup(func() { gotOnUpdateCallCount = 0 }) + if tt.wantIsErr != 0 { + require.Equal(db.NoRowsAffected, gotNumUpdated) + require.Truef(errors.Match(errors.T(tt.wantIsErr), err), "want err: %q got: %q", tt.wantIsErr, err) + return + } + require.NoError(err) + require.Equal(1, gotOnUpdateCallCount) + assert.Equal(1, gotNumUpdated) + + // Quick assertion that the set is not nil and that the plugin + // ID in the catalog referenced by the set matches the plugin + // ID in the returned plugin. + require.NotNil(gotUpdatedSet) + require.NotNil(gotPlugin) + assert.Equal(testCatalog.PublicId, gotUpdatedSet.CatalogId) + assert.Equal(testCatalog.PluginId, gotPlugin.PublicId) + + // Also assert that the hosts returned by the request are the ones that belong to the set + wantHostMap := make(map[string]string, len(wantedHosts)) + for _, h := range wantedHosts { + wantHostMap[h.PublicId] = h.ExternalId + } + gotHostMap := make(map[string]string, len(gotHosts)) + for _, h := range gotHosts { + gotHostMap[h.PublicId] = h.ExternalId + } + assert.Equal(wantHostMap, gotHostMap) + + // Perform checks + for _, check := range tt.wantCheckSetFuncs { + check(t, gotUpdatedSet) + } + + gotLookupSet, gotPlugin, err := repo.LookupSet(ctx, workingSet.GetPublicId()) + assert.Empty(cmp.Diff(gotUpdatedSet, gotLookupSet, protocmp.Transform())) + }) + } +} + +func TestRepository_LookupSet(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + sched := scheduler.TestScheduler(t, conn, wrapper) + _, prj := iam.TestScopes(t, iamRepo) + plg := hostplg.TestPlugin(t, conn, "lookup") + plgm := map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): NewWrappingPluginClient(&TestPluginServer{}), + } + + catalog := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + hostSet := TestSet(t, conn, kms, sched, catalog, map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): NewWrappingPluginClient(&TestPluginServer{ + ListHostsFn: func(ctx context.Context, req *plgpb.ListHostsRequest) (*plgpb.ListHostsResponse, error) { + require.NotEmpty(t, req.GetSets()) + require.NotNil(t, req.GetCatalog()) + return &plgpb.ListHostsResponse{}, nil + }, + }), + }, WithSyncIntervalSeconds(5)) + hostSetId, err := newHostSetId(ctx) + require.NoError(t, err) + + tests := []struct { + name string + in string + want *HostSet + wantIsErr errors.Code + }{ + { + name: "with-no-public-id", + wantIsErr: errors.InvalidParameter, + }, + { + name: "with-non-existing-host-set-id", + in: hostSetId, + }, + { + name: "with-existing-host-set-id", + in: hostSet.PublicId, + want: hostSet, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + repo, err := NewRepository(rw, rw, kms, sched, plgm) + assert.NoError(err) + require.NotNil(repo) + got, _, err := repo.LookupSet(ctx, tt.in) + if tt.wantIsErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "want err: %q got: %q", tt.wantIsErr, err) + assert.Nil(got) + return + } + require.NoError(err) + if tt.want != nil { + assert.Empty(cmp.Diff(got, tt.want, protocmp.Transform()), "LookupSet(%q) got response %q, wanted %q", tt.in, got, tt.want) + } + }) + } +} + +func TestRepository_Endpoints(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + sched := scheduler.TestScheduler(t, conn, wrapper) + _, prj := iam.TestScopes(t, iamRepo) + plg := hostplg.TestPlugin(t, conn, "endpoints") + + hostlessCatalog := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + plgm := map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): NewWrappingPluginClient(&TestPluginServer{}), + } + + catalog := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + hostSet10 := TestSet(t, conn, kms, sched, catalog, plgm, WithName("hostSet10"), WithPreferredEndpoints([]string{"cidr:10.0.0.1/24"})) + hostSet192 := TestSet(t, conn, kms, sched, catalog, plgm, WithName("hostSet192"), WithPreferredEndpoints([]string{"cidr:192.168.0.1/24"})) + hostSet100 := TestSet(t, conn, kms, sched, catalog, plgm, WithName("hostSet100"), WithPreferredEndpoints([]string{"cidr:100.100.100.100/24"})) + hostlessSet := TestSet(t, conn, kms, sched, hostlessCatalog, plgm) + + h1 := TestHost(t, conn, catalog.GetPublicId(), "test", withIpAddresses([]string{"10.0.0.5", "192.168.0.5"})) + TestSetMembers(t, conn, hostSet10.GetPublicId(), []*Host{h1}) + TestSetMembers(t, conn, hostSet192.GetPublicId(), []*Host{h1}) + TestSetMembers(t, conn, hostSet100.GetPublicId(), []*Host{h1}) + + tests := []struct { + name string + setIds []string + want []*host.Endpoint + wantIsErr errors.Code + }{ + { + name: "with-no-set-id", + wantIsErr: errors.InvalidParameter, + }, + { + name: "with-set10", + setIds: []string{hostSet10.GetPublicId()}, + want: []*host.Endpoint{ + { + HostId: func() string { + s, err := newHostId(ctx, catalog.GetPublicId(), "test") + require.NoError(t, err) + return s + }(), + SetId: hostSet10.GetPublicId(), + Address: "10.0.0.5", + }, + }, + }, + { + name: "with-different-set", + setIds: []string{hostSet192.GetPublicId()}, + want: []*host.Endpoint{ + { + HostId: func() string { + s, err := newHostId(ctx, catalog.GetPublicId(), "test") + require.NoError(t, err) + return s + }(), + SetId: hostSet192.GetPublicId(), + Address: "192.168.0.5", + }, + }, + }, + { + name: "with-all-addresses-filtered-set", + setIds: []string{hostSet100.GetPublicId()}, + want: nil, + }, + { + name: "with-no-hosts-from-plugin", + setIds: []string{hostlessSet.GetPublicId()}, + want: nil, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + repo, err := NewRepository(rw, rw, kms, sched, plgm) + assert.NoError(err) + require.NotNil(repo) + got, err := repo.Endpoints(ctx, tt.setIds) + if tt.wantIsErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "want err: %q got: %q", tt.wantIsErr, err) + assert.Nil(got) + return + } + require.NoError(err) + if tt.want == nil { + return + } + + sort.Slice(tt.want, func(i, j int) bool { + return tt.want[i].HostId < tt.want[j].HostId + }) + sort.Slice(got, func(i, j int) bool { + return got[i].HostId < got[j].HostId + }) + assert.Empty(cmp.Diff(got, tt.want, protocmp.Transform())) + + for _, ep := range got { + h := allocHost() + h.PublicId = ep.HostId + require.NoError(rw.LookupByPublicId(ctx, h)) + + assert.Equal(ep.HostId, h.PublicId) + // TODO: Uncomment when we have a better way to lookup host + // with it's address + // assert.Equal(ep.Address, h.Address) + assert.Equal(catalog.GetPublicId(), h.GetCatalogId()) + } + }) + } +} + +func TestRepository_ListSets(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + sched := scheduler.TestScheduler(t, conn, wrapper) + _, prj := iam.TestScopes(t, iamRepo) + plg := hostplg.TestPlugin(t, conn, "list") + plgm := map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): NewWrappingPluginClient(&TestPluginServer{}), + } + catalogA := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + catalogB := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + + hostSets := []*HostSet{ + TestSet(t, conn, kms, sched, catalogA, plgm), + TestSet(t, conn, kms, sched, catalogA, plgm), + TestSet(t, conn, kms, sched, catalogA, plgm), + } + + printoutTable(t, rw) + + tests := []struct { + name string + in string + opts []host.Option + want []*HostSet + wantIsErr errors.Code + }{ + { + name: "with-no-catalog-id", + wantIsErr: errors.InvalidParameter, + }, + { + name: "Catalog-with-no-host-sets", + in: catalogB.PublicId, + want: nil, + }, + { + name: "Catalog-with-host-sets", + in: catalogA.PublicId, + want: hostSets, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + repo, err := NewRepository(rw, rw, kms, sched, plgm) + assert.NoError(err) + require.NotNil(repo) + got, gotPlg, err := repo.ListSets(context.Background(), tt.in, tt.opts...) + if tt.wantIsErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "want err: %q got: %q", tt.wantIsErr, err) + assert.Nil(got) + return + } + require.NoError(err) + opts := []cmp.Option{ + cmpopts.SortSlices(func(x, y *HostSet) bool { return x.PublicId < y.PublicId }), + protocmp.Transform(), + } + assert.Empty(cmp.Diff(tt.want, got, opts...)) + if got != nil { + assert.Empty(cmp.Diff(plg, gotPlg, protocmp.Transform())) + } + }) + } +} + +func TestRepository_ListSets_Limits(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + sched := scheduler.TestScheduler(t, conn, wrapper) + _, prj := iam.TestScopes(t, iamRepo) + plg := hostplg.TestPlugin(t, conn, "listlimit") + plgm := map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): NewWrappingPluginClient(&TestPluginServer{}), + } + catalog := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + count := 10 + var hostSets []*HostSet + for i := 0; i < count; i++ { + hostSets = append(hostSets, TestSet(t, conn, kms, sched, catalog, plgm)) + } + + tests := []struct { + name string + repoOpts []host.Option + listOpts []host.Option + wantLen int + }{ + { + name: "With no limits", + wantLen: count, + }, + { + name: "With repo limit", + repoOpts: []host.Option{host.WithLimit(3)}, + wantLen: 3, + }, + { + name: "With negative repo limit", + repoOpts: []host.Option{host.WithLimit(-1)}, + wantLen: count, + }, + { + name: "With List limit", + listOpts: []host.Option{host.WithLimit(3)}, + wantLen: 3, + }, + { + name: "With negative List limit", + listOpts: []host.Option{host.WithLimit(-1)}, + wantLen: count, + }, + { + name: "With repo smaller than list limit", + repoOpts: []host.Option{host.WithLimit(2)}, + listOpts: []host.Option{host.WithLimit(6)}, + wantLen: 6, + }, + { + name: "With repo larger than list limit", + repoOpts: []host.Option{host.WithLimit(6)}, + listOpts: []host.Option{host.WithLimit(2)}, + wantLen: 2, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + repo, err := NewRepository(rw, rw, kms, sched, plgm, tt.repoOpts...) + assert.NoError(err) + require.NotNil(repo) + got, gotPlg, err := repo.ListSets(context.Background(), hostSets[0].CatalogId, tt.listOpts...) + require.NoError(err) + assert.Len(got, tt.wantLen) + assert.Empty(cmp.Diff(plg, gotPlg, protocmp.Transform())) + }) + } +} + +func TestRepository_DeleteSet(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + sched := scheduler.TestScheduler(t, conn, wrapper) + _, prj := iam.TestScopes(t, iamRepo) + plg := hostplg.TestPlugin(t, conn, "create") + + plgm := map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): NewWrappingPluginClient(TestPluginServer{OnCreateSetFn: func(ctx context.Context, req *plgpb.OnCreateSetRequest) (*plgpb.OnCreateSetResponse, error) { + return &plgpb.OnCreateSetResponse{}, nil + }}), + } + c := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + hostSet := TestSet(t, conn, kms, sched, c, plgm) + hostSet2 := TestSet(t, conn, kms, sched, c, plgm) + + newHostSetId, err := newHostSetId(ctx) + require.NoError(t, err) + tests := []struct { + name string + in string + pluginChecker func(*plgpb.OnDeleteSetRequest) error + want int + wantIsErr errors.Code + }{ + { + name: "With no public id", + pluginChecker: func(req *plgpb.OnDeleteSetRequest) error { + assert.Fail(t, "The plugin shouldn't be called") + return nil + }, + wantIsErr: errors.InvalidParameter, + }, + { + name: "With non existing host set id", + in: newHostSetId, + pluginChecker: func(req *plgpb.OnDeleteSetRequest) error { + assert.Fail(t, "The plugin shouldn't be called") + return nil + }, + want: 0, + }, + { + name: "With existing host set id", + in: hostSet.PublicId, + pluginChecker: func(req *plgpb.OnDeleteSetRequest) error { + assert.Equal(t, c.GetPublicId(), req.GetCatalog().GetId()) + assert.Equal(t, hostSet.GetPublicId(), req.GetSet().GetId()) + return nil + }, + want: 1, + }, + { + name: "Ignores plugin errors", + in: hostSet2.PublicId, + pluginChecker: func(req *plgpb.OnDeleteSetRequest) error { + assert.Equal(t, c.GetPublicId(), req.GetCatalog().GetId()) + assert.Equal(t, hostSet2.GetPublicId(), req.GetSet().GetId()) + return fmt.Errorf("test error") + }, + want: 1, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + repo, err := NewRepository(rw, rw, kms, sched, plgm) + assert.NoError(err) + require.NotNil(repo) + got, err := repo.DeleteSet(ctx, prj.PublicId, tt.in) + if tt.wantIsErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "want err: %q got: %q", tt.wantIsErr, err) + assert.Zero(got) + return + } + require.NoError(err) + assert.EqualValues(tt.want, got) + }) + } +} + +func printoutTable(t *testing.T, rw *db.Db) { + ctx := context.Background() + hsas := []*hostSetAgg{} + require.NoError(t, rw.SearchWhere(ctx, &hsas, "", nil)) + for _, hs := range hsas { + t.Logf("%#v", hs) + } +} diff --git a/internal/host/plugin/repository_host_test.go b/internal/host/plugin/repository_host_test.go new file mode 100644 index 000000000..002d2f660 --- /dev/null +++ b/internal/host/plugin/repository_host_test.go @@ -0,0 +1,294 @@ +package plugin + +import ( + "context" + "fmt" + "sort" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/db/timestamp" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/host/plugin/store" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/oplog" + hostplg "github.com/hashicorp/boundary/internal/plugin/host" + hostplgstore "github.com/hashicorp/boundary/internal/plugin/host/store" + "github.com/hashicorp/boundary/internal/scheduler" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestJob_UpsertHosts(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + sched := scheduler.TestScheduler(t, conn, wrapper) + kms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + _, prj := iam.TestScopes(t, iamRepo) + + plg := hostplg.TestPlugin(t, conn, "create") + plgm := map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): NewWrappingPluginClient(&plgpb.UnimplementedHostPluginServiceServer{}), + } + + catalog := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + const setCount int = 3 + setIds := make([]string, 0, setCount) + for i := 0; i < setCount; i++ { + set := TestSet(t, conn, kms, sched, catalog, plgm) + setIds = append(setIds, set.GetPublicId()) + } + sort.Strings(setIds) + phs, exp := TestExternalHosts(t, catalog, setIds, setCount) + phs[2].Name = phs[0].Name + exp[2].Name = exp[0].Name + + type input struct { + catalog *HostCatalog + sets []string + phs []*plgpb.ListHostsResponseHost + exp []*Host + } + + tests := []struct { + name string + in func() *input + opts []Option + wantIsErr errors.Code + }{ + { + name: "nil-hosts", + in: func() *input { + return &input{ + catalog: catalog, + sets: setIds, + } + }, + wantIsErr: errors.InvalidParameter, + }, + { + name: "no-external-id-hosts", + in: func() *input { + testPhs, _ := TestExternalHosts(t, catalog, setIds, setCount) + testPhs[1].ExternalId = "" + return &input{ + catalog: catalog, + sets: setIds, + phs: testPhs, + } + }, + wantIsErr: errors.InvalidParameter, + }, + { + name: "nil-catalog", + in: func() *input { + return &input{ + sets: setIds, + phs: phs, + } + }, + wantIsErr: errors.InvalidParameter, + }, + { + name: "no-catalog-id", + in: func() *input { + cat := catalog.clone() + cat.PublicId = "" + return &input{ + catalog: cat, + sets: setIds, + phs: phs, + } + }, + wantIsErr: errors.InvalidParameter, + }, + { + name: "no-scope-id", + in: func() *input { + cat := catalog.clone() + cat.ScopeId = "" + return &input{ + catalog: cat, + sets: setIds, + phs: phs, + } + }, + wantIsErr: errors.InvalidParameter, + }, + { + name: "nil-sets", + in: func() *input { + return &input{ + catalog: catalog, + phs: phs, + } + }, + wantIsErr: errors.InvalidParameter, + }, + { + name: "no-sets", + in: func() *input { + return &input{ + catalog: catalog, + sets: make([]string, 0), + phs: phs, + } + }, + wantIsErr: errors.InvalidParameter, + }, + { + name: "valid", // Note: this also tests duplicate names + in: func() *input { + return &input{ + catalog: catalog, + sets: setIds, + phs: phs, + exp: exp, + } + }, + }, + { + name: "valid-changed-values", + in: func() *input { + ph := phs[1] + e := exp[1] + newIp := testGetIpAddress(t) + newName := testGetDnsName(t) + ph.IpAddresses = append(ph.IpAddresses, newIp) + e.IpAddresses = append(e.IpAddresses, newIp) + ph.DnsNames = append(ph.DnsNames, newName) + e.DnsNames = append(e.DnsNames, newName) + // These are sorted by the repo function, so we need to match + sort.Strings(e.IpAddresses) + sort.Strings(e.DnsNames) + + ph.Name, ph.Description = ph.Description, ph.Name + e.Name, e.Description = e.Description, e.Name + + ph.SetIds = ph.SetIds[0 : len(ph.SetIds)-1] + e.SetIds = e.SetIds[0 : len(e.SetIds)-1] + return &input{ + catalog: catalog, + sets: setIds, + phs: phs, + exp: exp, + } + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + job, err := newSetSyncJob(ctx, rw, rw, kms, plgm) + require.NoError(err) + require.NotNil(job) + in := tt.in() + got, err := job.upsertHosts(ctx, in.catalog, in.sets, in.phs, tt.opts...) + if tt.wantIsErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "want err: %q got: %q", tt.wantIsErr, err) + assert.Nil(got) + return + } + require.NoError(err, fmt.Sprintf("%v", in.catalog)) + require.NotNil(got) + + // Basic tests + assert.Len(got, len(in.phs)) + for _, h := range in.exp { + assert.NoError(db.TestVerifyOplog(t, rw, h.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second))) + } + + // Make sure outputs match. Ignore timestamps. + assert.Empty( + cmp.Diff( + in.exp, + got, + cmpopts.IgnoreUnexported(Host{}, store.Host{}), + cmpopts.IgnoreTypes(×tamp.Timestamp{}), + cmpopts.SortSlices(func(x, y *Host) bool { + return x.GetPublicId() < y.GetPublicId() + }), + ), + ) + + repo, err := NewRepository(rw, rw, kms, sched, plgm) + require.NoError(err) + require.NotNil(repo) + + // Check again, but via performing an explicit list + var gotPlg *hostplg.Plugin + got, gotPlg, err = repo.ListHostsByCatalogId(ctx, in.catalog.GetPublicId()) + require.NoError(err) + require.NotNil(got) + assert.Len(got, len(in.phs)) + assert.Empty( + cmp.Diff( + in.exp, + got, + cmpopts.IgnoreUnexported(Host{}, store.Host{}), + cmpopts.IgnoreTypes(×tamp.Timestamp{}), + cmpopts.SortSlices(func(x, y *Host) bool { + return x.GetPublicId() < y.GetPublicId() + }), + ), + ) + assert.Empty( + cmp.Diff( + plg, + gotPlg, + cmpopts.IgnoreUnexported(hostplg.Plugin{}, hostplgstore.Plugin{}), + cmpopts.IgnoreTypes(×tamp.Timestamp{}), + ), + ) + + // Now individually call read on each host, cache the matching set + // IDs, and then check membership + setIdMap := make(map[string][]string) + for _, exp := range in.exp { + for _, setId := range exp.SetIds { + setIdMap[setId] = append(setIdMap[setId], exp.GetPublicId()) + } + got, gotPlg, err := repo.LookupHost(ctx, exp.GetPublicId()) + require.NoError(err) + require.NotNil(got) + assert.NotEmpty(got.SetIds) + assert.Empty( + cmp.Diff( + exp, + got, + cmpopts.IgnoreUnexported(Host{}, store.Host{}), + cmpopts.IgnoreTypes(×tamp.Timestamp{}), + ), + ) + assert.Empty( + cmp.Diff( + plg, + gotPlg, + cmpopts.IgnoreUnexported(hostplg.Plugin{}, hostplgstore.Plugin{}), + cmpopts.IgnoreTypes(×tamp.Timestamp{}), + ), + ) + } + for setId, expHostIds := range setIdMap { + got, err = repo.ListHostsBySetIds(ctx, []string{setId}) + require.NoError(err) + require.NotNil(got) + var gotHostIds []string + for _, h := range got { + gotHostIds = append(gotHostIds, h.GetPublicId()) + } + assert.ElementsMatch(expHostIds, gotHostIds) + } + }) + } +} diff --git a/internal/host/plugin/repository_host_util.go b/internal/host/plugin/repository_host_util.go new file mode 100644 index 000000000..fb8cd97db --- /dev/null +++ b/internal/host/plugin/repository_host_util.go @@ -0,0 +1,262 @@ +package plugin + +import ( + "context" + "sort" + + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/host" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" + "github.com/hashicorp/go-secure-stdlib/strutil" +) + +// valueToInterfaceMap is a map that has a function to convert values into an +// array +type valueToInterfaceMap map[string]interface{} + +func (m valueToInterfaceMap) toArray() []interface{} { + switch { + case m == nil: + return nil + case len(m) == 0: + return make([]interface{}, 0) + default: + ret := make([]interface{}, 0, len(m)) + for _, v := range m { + ret = append(ret, v) + } + return ret + } +} + +// hostInfo stores the info we need for figuring out host, set membership, +// and value object differences. It also stores dirty flags to indicate +// whether we need to update value objects or the host itself. +type hostInfo struct { + h *Host + ipsToAdd valueToInterfaceMap + ipsToRemove valueToInterfaceMap + dnsNamesToAdd valueToInterfaceMap + dnsNamesToRemove valueToInterfaceMap + dirtyHost bool +} + +func createNewHostMap(ctx context.Context, + catalog *HostCatalog, + phs []*plgpb.ListHostsResponseHost, + currentHostMap map[string]*Host) (map[string]*hostInfo, error) { + + const op = "plugin.createNewHostMap" + newHostMap := make(map[string]*hostInfo, len(phs)) + + var err error + for _, ph := range phs { + newHost := NewHost(ctx, + catalog.GetPublicId(), + ph.GetExternalId(), + WithName(ph.GetName()), + WithDescription(ph.GetDescription()), + withIpAddresses(ph.GetIpAddresses()), + withDnsNames(ph.GetDnsNames()), + withPluginId(catalog.GetPluginId())) + newHost.PublicId, err = newHostId(ctx, catalog.GetPublicId(), ph.GetExternalId()) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + newHost.SetIds = ph.SetIds + hi := &hostInfo{ + h: newHost, + } + newHostMap[newHost.PublicId] = hi + + // Check if the host is dirty; that is, we need to perform an upsert + // operation. If the host isn't dirty, we have nothing to do. Note + // that we don't check every value exhaustively; for instance, we + // assume catalog ID and external ID don't change because if they do + // the public ID will be different as well. + currHost := currentHostMap[newHost.PublicId] + switch { + case currHost == nil, + currHost.Name != newHost.Name, + currHost.Description != newHost.Description: + hi.dirtyHost = true + } + + // Get the current set of host IPs/DNS names for comparison. These + // will be in sorted order since ordering is kept in the database + // and they will have been sorted before insertion. + var currHostIps []string + var currHostDnsNames []string + if currHost != nil { + currHostIps = currHost.IpAddresses + currHostDnsNames = currHost.DnsNames + } + + // Sort these here before comparison. We always use a priority order + // based on the behavior of sort.Strings so that we can check for + // equivalency. + sort.Strings(newHost.IpAddresses) + sort.Strings(newHost.DnsNames) + + // IPs + { + switch { + case strutil.EquivalentSlices(currHostIps, newHost.GetIpAddresses()): + // Nothing to do...don't remove or add anything + + default: + // No match, so we need to remove the old ones and add the new + + // First, build up removals + for _, ip := range currHostIps { + if hi.ipsToRemove == nil { + hi.ipsToRemove = make(valueToInterfaceMap, len(currHostIps)) + } + obj, err := host.NewIpAddress(ctx, newHost.PublicId, ip) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + hi.ipsToRemove[ip] = obj + } + + // Now build up additions + for _, ip := range newHost.GetIpAddresses() { + // If it's in ipsToRemove we found it on the current host; + // finding it again here means the host still has it, so + // simply do no change. + if hi.ipsToRemove != nil && hi.ipsToRemove[ip] != nil { + delete(hi.ipsToRemove, ip) + if len(hi.ipsToRemove) == 0 { + hi.ipsToRemove = nil + } + continue + } + if hi.ipsToAdd == nil { + hi.ipsToAdd = make(valueToInterfaceMap, len(newHost.GetIpAddresses())) + } + obj, err := host.NewIpAddress(ctx, newHost.PublicId, ip) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + hi.ipsToAdd[ip] = obj + } + } + } + + // DNS names + { + switch { + case strutil.EquivalentSlices(currHostDnsNames, newHost.GetDnsNames()): + // Nothing to do...don't remove or add anything + + default: + // No match, so we need to remove the old ones and add the new + + // First, build up removals + for _, name := range currHostDnsNames { + if hi.dnsNamesToRemove == nil { + hi.dnsNamesToRemove = make(valueToInterfaceMap, len(currHostDnsNames)) + } + obj, err := host.NewDnsName(ctx, newHost.PublicId, name) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + hi.dnsNamesToRemove[name] = obj + } + + // Now build up additions + for _, name := range newHost.GetDnsNames() { + // If it's in dnsNamesToRemove we found it on the current + // host; finding it again here means the host still has it, + // so simply do no change. + if hi.dnsNamesToRemove != nil && hi.dnsNamesToRemove[name] != nil { + delete(hi.dnsNamesToRemove, name) + if len(hi.dnsNamesToRemove) == 0 { + hi.dnsNamesToRemove = nil + } + continue + } + if hi.dnsNamesToAdd == nil { + hi.dnsNamesToAdd = make(valueToInterfaceMap, len(newHost.GetDnsNames())) + } + obj, err := host.NewDnsName(ctx, newHost.PublicId, name) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + hi.dnsNamesToAdd[name] = obj + } + } + } + } + + return newHostMap, nil +} + +func getSetChanges( + currentHostMap map[string]*Host, + newHostMap map[string]*hostInfo) ( + setMembershipsToAdd, setMembershipsToRemove map[string][]string, + allSetIds map[string]struct{}) { + + // First, find sets that hosts should be added to: hosts that are new or + // have new set IDs returned. + for newHostId, newHost := range newHostMap { + var setsToAdd []string + currentHost, ok := currentHostMap[newHostId] + if !ok { + // If the host was not known about before now, any sets the host + // matches will need to be added + setsToAdd = newHost.h.SetIds + } else { + // Otherwise, add to any it doesn't currently match + for _, setId := range newHost.h.SetIds { + if !strutil.StrListContains(currentHost.SetIds, setId) { + setsToAdd = append(setsToAdd, setId) + } + } + } + // Add to the total set + for _, setToAdd := range setsToAdd { + if setMembershipsToAdd == nil { + setMembershipsToAdd = make(map[string][]string) + } + if allSetIds == nil { + allSetIds = make(map[string]struct{}) + } + setMembershipsToAdd[setToAdd] = append(setMembershipsToAdd[setToAdd], newHostId) + allSetIds[setToAdd] = struct{}{} + } + } + + // Now, do the inverse: remove hosts from sets that appear there now but no + // longer have that set ID in their current list. + for currentHostId, currentHost := range currentHostMap { + var setsToRemove []string + newHost, ok := newHostMap[currentHostId] + if !ok { + // If the host doesn't even appear now, we obviously want to remove + // it from all existing set memberships + setsToRemove = currentHost.SetIds + } else { + // Otherwise, remove it from any it doesn't currently have + for _, setId := range currentHost.SetIds { + if !strutil.StrListContains(newHost.h.SetIds, setId) { + setsToRemove = append(setsToRemove, setId) + } + } + } + // Add to the total set + for _, setToRemove := range setsToRemove { + if setMembershipsToRemove == nil { + setMembershipsToRemove = make(map[string][]string) + } + if allSetIds == nil { + allSetIds = make(map[string]struct{}) + } + setMembershipsToRemove[setToRemove] = append(setMembershipsToRemove[setToRemove], currentHostId) + allSetIds[setToRemove] = struct{}{} + } + } + + return +} diff --git a/internal/host/plugin/repository_host_util_test.go b/internal/host/plugin/repository_host_util_test.go new file mode 100644 index 000000000..fe852492d --- /dev/null +++ b/internal/host/plugin/repository_host_util_test.go @@ -0,0 +1,312 @@ +package plugin + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/boundary/internal/host" + hoststore "github.com/hashicorp/boundary/internal/host/store" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" +) + +func TestUtilFunctions(t *testing.T) { + ctx := context.Background() + const externalId = "ext_1234567890" + const pluginId = "plg_1234567890" + + catalog, err := NewHostCatalog(ctx, "p_1234567890", pluginId) + require.NoError(t, err) + require.NotNil(t, catalog) + catalog.PublicId = "hc_1234567890" + baseHostId, err := newHostId(ctx, catalog.GetPublicId(), externalId) + require.NoError(t, err) + + baseResponseHost := &plgpb.ListHostsResponseHost{ + ExternalId: externalId, + Name: "base-name", + Description: "base-description", + IpAddresses: []string{"1.2.3.4", "5.6.7.8"}, + DnsNames: []string{"a.b.c", "x.y.z"}, + SetIds: []string{"set1", "set2"}, + } + baseIpsIfaces := valueToInterfaceMap{} + for _, v := range baseResponseHost.IpAddresses { + ip, err := host.NewIpAddress(ctx, baseHostId, v) + require.NoError(t, err) + baseIpsIfaces[v] = ip + } + baseDnsNamesIfaces := valueToInterfaceMap{} + for _, v := range baseResponseHost.DnsNames { + name, err := host.NewDnsName(ctx, baseHostId, v) + require.NoError(t, err) + baseDnsNamesIfaces[v] = name + } + baseHost := NewHost(ctx, catalog.PublicId, externalId) + baseHost.Name = baseResponseHost.Name + baseHost.Description = baseResponseHost.Description + baseHost.IpAddresses = baseResponseHost.IpAddresses + baseHost.DnsNames = baseResponseHost.DnsNames + baseHost.PluginId = pluginId + baseHost.SetIds = baseResponseHost.SetIds + + defaultHostFunc := func(in *Host) *Host { + return in + } + defaultSetsFunc := func() []string { + return baseHost.SetIds + } + + tests := []struct { + name string + host func(*Host) *Host + // This function should take in a base host (which will be provided as a + // clone) and transform however needed for the particular test + in func(*plgpb.ListHostsResponseHost) (*plgpb.ListHostsResponseHost, *hostInfo) + sets func() []string + setsToAdd map[string][]string + setsToRemove map[string][]string + }{ + { + name: "base", + host: func(in *Host) *Host { + return nil + }, + sets: func() []string { + return nil + }, + in: func(in *plgpb.ListHostsResponseHost) (*plgpb.ListHostsResponseHost, *hostInfo) { + hi := &hostInfo{ + dirtyHost: true, + ipsToAdd: baseIpsIfaces, + dnsNamesToAdd: baseDnsNamesIfaces, + } + return in, hi + }, + }, + { + name: "no-change", + host: defaultHostFunc, + sets: defaultSetsFunc, + in: func(in *plgpb.ListHostsResponseHost) (*plgpb.ListHostsResponseHost, *hostInfo) { + hi := &hostInfo{} + return in, hi + }, + }, + { + name: "new-name", + host: defaultHostFunc, + sets: defaultSetsFunc, + in: func(in *plgpb.ListHostsResponseHost) (*plgpb.ListHostsResponseHost, *hostInfo) { + in.Name = "newname" + hi := &hostInfo{ + dirtyHost: true, + } + return in, hi + }, + }, + { + name: "new-description", + host: defaultHostFunc, + sets: defaultSetsFunc, + in: func(in *plgpb.ListHostsResponseHost) (*plgpb.ListHostsResponseHost, *hostInfo) { + in.Description = "newdescription" + hi := &hostInfo{ + dirtyHost: true, + } + return in, hi + }, + }, + { + name: "extra-ip", + host: defaultHostFunc, + sets: defaultSetsFunc, + in: func(in *plgpb.ListHostsResponseHost) (*plgpb.ListHostsResponseHost, *hostInfo) { + const newIp = "11.22.33.44" + in.IpAddresses = append(in.IpAddresses, newIp) + ip, err := host.NewIpAddress(ctx, baseHostId, newIp) + require.NoError(t, err) + hi := &hostInfo{ + ipsToAdd: valueToInterfaceMap{newIp: ip}, + } + return in, hi + }, + }, + { + name: "remove-ip", + host: defaultHostFunc, + sets: defaultSetsFunc, + in: func(in *plgpb.ListHostsResponseHost) (*plgpb.ListHostsResponseHost, *hostInfo) { + hi := &hostInfo{ + ipsToRemove: valueToInterfaceMap{}, + } + for _, ip := range in.IpAddresses[1:] { + hi.ipsToRemove[ip] = baseIpsIfaces[ip] + } + in.IpAddresses = in.IpAddresses[0:1] + return in, hi + }, + }, + { + name: "extra-dns-name", + host: defaultHostFunc, + sets: defaultSetsFunc, + in: func(in *plgpb.ListHostsResponseHost) (*plgpb.ListHostsResponseHost, *hostInfo) { + const newName = "this.is.a.test" + in.DnsNames = append(in.DnsNames, newName) + name, err := host.NewDnsName(ctx, baseHostId, newName) + require.NoError(t, err) + hi := &hostInfo{ + dnsNamesToAdd: valueToInterfaceMap{newName: name}, + } + return in, hi + }, + }, + { + name: "remove-dns-name", + host: defaultHostFunc, + sets: defaultSetsFunc, + in: func(in *plgpb.ListHostsResponseHost) (*plgpb.ListHostsResponseHost, *hostInfo) { + hi := &hostInfo{ + dnsNamesToRemove: valueToInterfaceMap{}, + } + for _, name := range in.DnsNames[1:] { + hi.dnsNamesToRemove[name] = baseDnsNamesIfaces[name] + } + in.DnsNames = in.DnsNames[0:1] + return in, hi + }, + }, + { + name: "add-sets", + host: defaultHostFunc, + sets: func() []string { + return append(defaultSetsFunc(), "extra-set", "extra-set-2") + }, + in: func(in *plgpb.ListHostsResponseHost) (*plgpb.ListHostsResponseHost, *hostInfo) { + hi := &hostInfo{} + return in, hi + }, + setsToAdd: map[string][]string{ + "extra-set": {baseHostId}, + "extra-set-2": {baseHostId}, + }, + }, + { + name: "remove-sets", + host: defaultHostFunc, + sets: func() []string { + return defaultSetsFunc()[0:1] + }, + in: func(in *plgpb.ListHostsResponseHost) (*plgpb.ListHostsResponseHost, *hostInfo) { + hi := &hostInfo{} + return in, hi + }, + setsToRemove: map[string][]string{ + "set2": {baseHostId}, + }, + }, + { + name: "add-and-remove-sets", + host: defaultHostFunc, + sets: func() []string { + return append(defaultSetsFunc()[0:1], "extra-set") + }, + in: func(in *plgpb.ListHostsResponseHost) (*plgpb.ListHostsResponseHost, *hostInfo) { + hi := &hostInfo{} + return in, hi + }, + setsToAdd: map[string][]string{ + "extra-set": {baseHostId}, + }, + setsToRemove: map[string][]string{ + "set2": {baseHostId}, + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + require, assert := require.New(t), assert.New(t) + var hi *hostInfo + currHost := tt.host(baseHost.clone()) + currentHostMap := map[string]*Host{} + if currHost != nil { + currentHostMap[baseHostId] = currHost + } + h := proto.Clone(baseResponseHost).(*plgpb.ListHostsResponseHost) + h, hi = tt.in(h) + + newHostMap, err := createNewHostMap(ctx, catalog, []*plgpb.ListHostsResponseHost{h}, currentHostMap) + require.NoError(err) + require.NotNil(newHostMap) + require.NotNil(newHostMap[baseHostId]) + got := newHostMap[baseHostId] + + // Check the various host/hostinfo bits + { + assert.Equal(h.ExternalId, got.h.ExternalId) + assert.Equal(h.Name, got.h.Name) + assert.Equal(h.Description, got.h.Description) + assert.ElementsMatch(h.IpAddresses, got.h.IpAddresses) + assert.ElementsMatch(h.DnsNames, got.h.DnsNames) + assert.ElementsMatch(h.SetIds, got.h.SetIds) + + assert.Equal(hi.dirtyHost, got.dirtyHost) + assert.Empty( + cmp.Diff( + hi.ipsToAdd, + got.ipsToAdd, + cmpopts.IgnoreUnexported(host.IpAddress{}, hoststore.IpAddress{}), + cmpopts.SortSlices(func(x, y interface{}) bool { + return x.(*host.IpAddress).Address < y.(*host.IpAddress).Address + }), + ), + ) + assert.Empty( + cmp.Diff( + hi.ipsToRemove, + got.ipsToRemove, + cmpopts.IgnoreUnexported(host.IpAddress{}, hoststore.IpAddress{}), + cmpopts.SortSlices(func(x, y interface{}) bool { + return x.(*host.IpAddress).Address < y.(*host.IpAddress).Address + }), + ), + ) + assert.Empty( + cmp.Diff( + hi.dnsNamesToAdd, + got.dnsNamesToAdd, + cmpopts.IgnoreUnexported(host.DnsName{}, hoststore.DnsName{}), + cmpopts.SortSlices(func(x, y interface{}) bool { + return x.(*host.DnsName).Name < y.(*host.DnsName).Name + }), + ), + ) + assert.Empty( + cmp.Diff( + hi.dnsNamesToRemove, + got.dnsNamesToRemove, + cmpopts.IgnoreUnexported(host.DnsName{}, hoststore.DnsName{}), + cmpopts.SortSlices(func(x, y interface{}) bool { + return x.(*host.DnsName).Name < y.(*host.DnsName).Name + }), + ), + ) + } + + // Run through the sets function + { + newHostMap[baseHostId].h.SetIds = tt.sets() + toAdd, toRemove, _ := getSetChanges(currentHostMap, newHostMap) + assert.Equal(tt.setsToAdd, toAdd) + assert.Equal(tt.setsToRemove, toRemove) + } + }) + } +} diff --git a/internal/host/plugin/repository_test.go b/internal/host/plugin/repository_test.go new file mode 100644 index 000000000..594882b00 --- /dev/null +++ b/internal/host/plugin/repository_test.go @@ -0,0 +1,166 @@ +package plugin + +import ( + "testing" + + "github.com/hashicorp/boundary/internal/scheduler" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/host" + "github.com/hashicorp/boundary/internal/kms" +) + +func TestRepository_New(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + sched := scheduler.TestScheduler(t, conn, wrapper) + + plgs := map[string]plgpb.HostPluginServiceClient{} + + type args struct { + r db.Reader + w db.Writer + kms *kms.Kms + scheduler *scheduler.Scheduler + plugins map[string]plgpb.HostPluginServiceClient + opts []host.Option + } + + tests := []struct { + name string + args args + want *Repository + wantIsErr errors.Code + }{ + { + name: "valid", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + scheduler: sched, + plugins: plgs, + }, + want: &Repository{ + reader: rw, + writer: rw, + kms: kmsCache, + scheduler: sched, + plugins: plgs, + defaultLimit: db.DefaultLimit, + }, + }, + { + name: "valid-with-limit", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + scheduler: sched, + plugins: plgs, + opts: []host.Option{host.WithLimit(5)}, + }, + want: &Repository{ + reader: rw, + writer: rw, + kms: kmsCache, + scheduler: sched, + plugins: plgs, + defaultLimit: 5, + }, + }, + { + name: "nil-reader", + args: args{ + r: nil, + w: rw, + kms: kmsCache, + scheduler: sched, + plugins: plgs, + }, + want: nil, + wantIsErr: errors.InvalidParameter, + }, + { + name: "nil-writer", + args: args{ + r: rw, + w: nil, + kms: kmsCache, + scheduler: sched, + plugins: plgs, + }, + want: nil, + wantIsErr: errors.InvalidParameter, + }, + { + name: "nil-kms", + args: args{ + r: rw, + w: rw, + kms: nil, + scheduler: sched, + plugins: plgs, + }, + want: nil, + wantIsErr: errors.InvalidParameter, + }, + { + name: "nil-plugins", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + scheduler: sched, + plugins: nil, + }, + want: nil, + wantIsErr: errors.InvalidParameter, + }, + { + name: "nil-scheduler", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + scheduler: nil, + plugins: plgs, + }, + want: nil, + wantIsErr: errors.InvalidParameter, + }, + { + name: "all-nils", + args: args{ + r: nil, + w: nil, + kms: nil, + scheduler: nil, + plugins: nil, + }, + want: nil, + wantIsErr: errors.InvalidParameter, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + got, err := NewRepository(tt.args.r, tt.args.w, tt.args.kms, tt.args.scheduler, tt.args.plugins, tt.args.opts...) + if tt.wantIsErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "want err: %q got: %q", tt.wantIsErr, err) + assert.Nil(got) + return + } + assert.NoError(err) + require.NotNil(got) + assert.Equal(tt.want, got) + }) + } +} diff --git a/internal/host/plugin/store/host.pb.go b/internal/host/plugin/store/host.pb.go new file mode 100644 index 000000000..0fa003b26 --- /dev/null +++ b/internal/host/plugin/store/host.pb.go @@ -0,0 +1,895 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.17.3 +// source: controller/storage/host/plugin/store/v1/host.proto + +// Package store provides protobufs for storing types in the plugin host +// package. + +package store + +import ( + timestamp "github.com/hashicorp/boundary/internal/db/timestamp" + _ "github.com/hashicorp/boundary/sdk/pbs/controller/protooptions" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + _ "google.golang.org/protobuf/types/known/structpb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type HostCatalog struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // public_id is a surrogate key suitable for use in a public API. + // @inject_tag: `gorm:"primary_key"` + PublicId string `protobuf:"bytes,1,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"` + // The create_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + CreateTime *timestamp.Timestamp `protobuf:"bytes,2,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"` + // The update_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + UpdateTime *timestamp.Timestamp `protobuf:"bytes,3,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" gorm:"default:current_timestamp"` + // name is optional. If set, it must be unique within scope_id. + // @inject_tag: `gorm:"default:null"` + Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty" gorm:"default:null"` + // description is optional. + // @inject_tag: `gorm:"default:null"` + Description string `protobuf:"bytes,5,opt,name=description,proto3" json:"description,omitempty" gorm:"default:null"` + // The scope_id of the owning scope and must be set. + // @inject_tag: `gorm:"not_null"` + ScopeId string `protobuf:"bytes,6,opt,name=scope_id,json=scopeId,proto3" json:"scope_id,omitempty" gorm:"not_null"` + // The public id of the plugin this catalog uses. + // @inject_tag: `gorm:"not_null"` + PluginId string `protobuf:"bytes,7,opt,name=plugin_id,json=pluginId,proto3" json:"plugin_id,omitempty" gorm:"not_null"` + // version allows optimistic locking of the resource + // @inject_tag: `gorm:"default:null"` + Version uint32 `protobuf:"varint,8,opt,name=version,proto3" json:"version,omitempty" gorm:"default:null"` + // attributes is a jsonb formatted field. + // @inject_tag: `gorm:"not_null"` + Attributes []byte `protobuf:"bytes,9,opt,name=attributes,proto3" json:"attributes,omitempty" gorm:"not_null"` +} + +func (x *HostCatalog) Reset() { + *x = HostCatalog{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HostCatalog) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HostCatalog) ProtoMessage() {} + +func (x *HostCatalog) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HostCatalog.ProtoReflect.Descriptor instead. +func (*HostCatalog) Descriptor() ([]byte, []int) { + return file_controller_storage_host_plugin_store_v1_host_proto_rawDescGZIP(), []int{0} +} + +func (x *HostCatalog) GetPublicId() string { + if x != nil { + return x.PublicId + } + return "" +} + +func (x *HostCatalog) GetCreateTime() *timestamp.Timestamp { + if x != nil { + return x.CreateTime + } + return nil +} + +func (x *HostCatalog) GetUpdateTime() *timestamp.Timestamp { + if x != nil { + return x.UpdateTime + } + return nil +} + +func (x *HostCatalog) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *HostCatalog) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *HostCatalog) GetScopeId() string { + if x != nil { + return x.ScopeId + } + return "" +} + +func (x *HostCatalog) GetPluginId() string { + if x != nil { + return x.PluginId + } + return "" +} + +func (x *HostCatalog) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *HostCatalog) GetAttributes() []byte { + if x != nil { + return x.Attributes + } + return nil +} + +type HostSet struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // public_id is a surrogate key suitable for use in a public API. + // @inject_tag: `gorm:"primary_key"` + PublicId string `protobuf:"bytes,1,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"` + // The create_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + CreateTime *timestamp.Timestamp `protobuf:"bytes,2,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"` + // The update_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + UpdateTime *timestamp.Timestamp `protobuf:"bytes,3,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" gorm:"default:current_timestamp"` + // The last_sync_time is updated every time a host set has been synced. + // @inject_tag: `gorm:"default:null"` + LastSyncTime *timestamp.Timestamp `protobuf:"bytes,4,opt,name=last_sync_time,json=lastSyncTime,proto3" json:"last_sync_time,omitempty" gorm:"default:null"` + // The need_sync indicates that a sync needs to happen. + // @inject_tag: `gorm:"not_null"` + NeedSync bool `protobuf:"varint,5,opt,name=need_sync,json=needSync,proto3" json:"need_sync,omitempty" gorm:"not_null"` + // name is optional. If set, it must be unique within + // catalog_id. + // @inject_tag: `gorm:"default:null"` + Name string `protobuf:"bytes,6,opt,name=name,proto3" json:"name,omitempty" gorm:"default:null"` + // description is optional. + // @inject_tag: `gorm:"default:null"` + Description string `protobuf:"bytes,7,opt,name=description,proto3" json:"description,omitempty" gorm:"default:null"` + // catalog_id is the public_id of the owning + // plugin_host_catalog and must be set. + // @inject_tag: `gorm:"not_null"` + CatalogId string `protobuf:"bytes,8,opt,name=catalog_id,json=catalogId,proto3" json:"catalog_id,omitempty" gorm:"not_null"` + // version allows optimistic locking of the resource + // @inject_tag: `gorm:"default:null"` + Version uint32 `protobuf:"varint,9,opt,name=version,proto3" json:"version,omitempty" gorm:"default:null"` + // attributes is a byte field containing marshaled JSON data + // @inject_tag: `gorm:"not_null"` + Attributes []byte `protobuf:"bytes,10,opt,name=attributes,proto3" json:"attributes,omitempty" gorm:"not_null"` + // preferred_endpoints stores string preference values + // @inject_tag: `gorm:"-"` + PreferredEndpoints []string `protobuf:"bytes,11,rep,name=preferred_endpoints,json=preferredEndpoints,proto3" json:"preferred_endpoints,omitempty" gorm:"-"` + // Sync interval is a value representing a duration in seconds + // @inject_tag: `gorm:"default:null"` + SyncIntervalSeconds int32 `protobuf:"varint,12,opt,name=sync_interval_seconds,json=syncIntervalSeconds,proto3" json:"sync_interval_seconds,omitempty" gorm:"default:null"` +} + +func (x *HostSet) Reset() { + *x = HostSet{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HostSet) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HostSet) ProtoMessage() {} + +func (x *HostSet) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HostSet.ProtoReflect.Descriptor instead. +func (*HostSet) Descriptor() ([]byte, []int) { + return file_controller_storage_host_plugin_store_v1_host_proto_rawDescGZIP(), []int{1} +} + +func (x *HostSet) GetPublicId() string { + if x != nil { + return x.PublicId + } + return "" +} + +func (x *HostSet) GetCreateTime() *timestamp.Timestamp { + if x != nil { + return x.CreateTime + } + return nil +} + +func (x *HostSet) GetUpdateTime() *timestamp.Timestamp { + if x != nil { + return x.UpdateTime + } + return nil +} + +func (x *HostSet) GetLastSyncTime() *timestamp.Timestamp { + if x != nil { + return x.LastSyncTime + } + return nil +} + +func (x *HostSet) GetNeedSync() bool { + if x != nil { + return x.NeedSync + } + return false +} + +func (x *HostSet) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *HostSet) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *HostSet) GetCatalogId() string { + if x != nil { + return x.CatalogId + } + return "" +} + +func (x *HostSet) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *HostSet) GetAttributes() []byte { + if x != nil { + return x.Attributes + } + return nil +} + +func (x *HostSet) GetPreferredEndpoints() []string { + if x != nil { + return x.PreferredEndpoints + } + return nil +} + +func (x *HostSet) GetSyncIntervalSeconds() int32 { + if x != nil { + return x.SyncIntervalSeconds + } + return 0 +} + +type HostCatalogSecret struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // catalog_id is the public id of the catalog containing this secret. + // @inject_tag: `gorm:"primary_key"` + CatalogId string `protobuf:"bytes,1,opt,name=catalog_id,json=catalogId,proto3" json:"catalog_id,omitempty" gorm:"primary_key"` + // The create_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + CreateTime *timestamp.Timestamp `protobuf:"bytes,2,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"` + // The update_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + UpdateTime *timestamp.Timestamp `protobuf:"bytes,3,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" gorm:"default:current_timestamp"` + // attributes is the plain-text of the attribute data. We are not storing + // this plain-text value in the database. + // @inject_tag: `gorm:"-" wrapping:"pt,secret_data"` + Secret []byte `protobuf:"bytes,4,opt,name=secret,proto3" json:"secret,omitempty" gorm:"-" wrapping:"pt,secret_data"` + // ct_attributes is the ciphertext of the attribute data stored in the db. + // @inject_tag: `gorm:"column:secret;not_null" wrapping:"ct,secret_data"` + CtSecret []byte `protobuf:"bytes,5,opt,name=ct_secret,json=ctSecret,proto3" json:"ct_secret,omitempty" gorm:"column:secret;not_null" wrapping:"ct,secret_data"` + // The key_id of the kms database key used for encrypting this entry. + // It must be set. + // @inject_tag: `gorm:"not_null"` + KeyId string `protobuf:"bytes,6,opt,name=key_id,json=keyId,proto3" json:"key_id,omitempty" gorm:"not_null"` +} + +func (x *HostCatalogSecret) Reset() { + *x = HostCatalogSecret{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HostCatalogSecret) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HostCatalogSecret) ProtoMessage() {} + +func (x *HostCatalogSecret) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HostCatalogSecret.ProtoReflect.Descriptor instead. +func (*HostCatalogSecret) Descriptor() ([]byte, []int) { + return file_controller_storage_host_plugin_store_v1_host_proto_rawDescGZIP(), []int{2} +} + +func (x *HostCatalogSecret) GetCatalogId() string { + if x != nil { + return x.CatalogId + } + return "" +} + +func (x *HostCatalogSecret) GetCreateTime() *timestamp.Timestamp { + if x != nil { + return x.CreateTime + } + return nil +} + +func (x *HostCatalogSecret) GetUpdateTime() *timestamp.Timestamp { + if x != nil { + return x.UpdateTime + } + return nil +} + +func (x *HostCatalogSecret) GetSecret() []byte { + if x != nil { + return x.Secret + } + return nil +} + +func (x *HostCatalogSecret) GetCtSecret() []byte { + if x != nil { + return x.CtSecret + } + return nil +} + +func (x *HostCatalogSecret) GetKeyId() string { + if x != nil { + return x.KeyId + } + return "" +} + +// TODO: Add a field which tracks if the host in cache should be considered +// invalid and fall back to the plugin provided data. +type Host struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // public_id is a surrogate key suitable for use in a public API. + // @inject_tag: `gorm:"primary_key"` + PublicId string `protobuf:"bytes,1,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"` + // external_id is an id provided by the plugin. + // @inject_tag: `gorm:"not_null"` + ExternalId string `protobuf:"bytes,2,opt,name=external_id,json=externalId,proto3" json:"external_id,omitempty" gorm:"not_null"` + // The create_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + CreateTime *timestamp.Timestamp `protobuf:"bytes,3,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"` + // The update_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + UpdateTime *timestamp.Timestamp `protobuf:"bytes,4,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" gorm:"default:current_timestamp"` + // name is optional. If set, it must be unique within + // catalog_id. + // @inject_tag: `gorm:"default:null"` + Name string `protobuf:"bytes,5,opt,name=name,proto3" json:"name,omitempty" gorm:"default:null"` + // description is optional. + // @inject_tag: `gorm:"default:null"` + Description string `protobuf:"bytes,6,opt,name=description,proto3" json:"description,omitempty" gorm:"default:null"` + // catalog_id is the public_id of the owning + // plugin_host_catalog and must be set. + // @inject_tag: `gorm:"not_null"` + CatalogId string `protobuf:"bytes,7,opt,name=catalog_id,json=catalogId,proto3" json:"catalog_id,omitempty" gorm:"not_null"` + // @inject_tag: `gorm:"-"` + Version uint32 `protobuf:"varint,8,opt,name=version,proto3" json:"version,omitempty" gorm:"-"` + // ip_addresses are the ip addresses associated with this host and will + // be persisted in the db through the HostAddress message. + // @inject_tag: `gorm:"-"` + IpAddresses []string `protobuf:"bytes,9,rep,name=ip_addresses,json=ipAddresses,proto3" json:"ip_addresses,omitempty" gorm:"-"` + // dns_names are the dns names associated with this host and will + // be persisted in the db through the HostAddress message. + // @inject_tag: `gorm:"-"` + DnsNames []string `protobuf:"bytes,10,rep,name=dns_names,json=dnsNames,proto3" json:"dns_names,omitempty" gorm:"-"` +} + +func (x *Host) Reset() { + *x = Host{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Host) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Host) ProtoMessage() {} + +func (x *Host) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Host.ProtoReflect.Descriptor instead. +func (*Host) Descriptor() ([]byte, []int) { + return file_controller_storage_host_plugin_store_v1_host_proto_rawDescGZIP(), []int{3} +} + +func (x *Host) GetPublicId() string { + if x != nil { + return x.PublicId + } + return "" +} + +func (x *Host) GetExternalId() string { + if x != nil { + return x.ExternalId + } + return "" +} + +func (x *Host) GetCreateTime() *timestamp.Timestamp { + if x != nil { + return x.CreateTime + } + return nil +} + +func (x *Host) GetUpdateTime() *timestamp.Timestamp { + if x != nil { + return x.UpdateTime + } + return nil +} + +func (x *Host) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Host) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *Host) GetCatalogId() string { + if x != nil { + return x.CatalogId + } + return "" +} + +func (x *Host) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *Host) GetIpAddresses() []string { + if x != nil { + return x.IpAddresses + } + return nil +} + +func (x *Host) GetDnsNames() []string { + if x != nil { + return x.DnsNames + } + return nil +} + +type HostSetMember struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // @inject_tag: `gorm:"primary_key"` + HostId string `protobuf:"bytes,1,opt,name=host_id,json=hostId,proto3" json:"host_id,omitempty" gorm:"primary_key"` + // @inject_tag: `gorm:"primary_key"` + SetId string `protobuf:"bytes,2,opt,name=set_id,json=setId,proto3" json:"set_id,omitempty" gorm:"primary_key"` + // @inject_tag: `gorm:"default:null"` + CatalogId string `protobuf:"bytes,3,opt,name=catalog_id,json=catalogId,proto3" json:"catalog_id,omitempty" gorm:"default:null"` +} + +func (x *HostSetMember) Reset() { + *x = HostSetMember{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HostSetMember) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HostSetMember) ProtoMessage() {} + +func (x *HostSetMember) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HostSetMember.ProtoReflect.Descriptor instead. +func (*HostSetMember) Descriptor() ([]byte, []int) { + return file_controller_storage_host_plugin_store_v1_host_proto_rawDescGZIP(), []int{4} +} + +func (x *HostSetMember) GetHostId() string { + if x != nil { + return x.HostId + } + return "" +} + +func (x *HostSetMember) GetSetId() string { + if x != nil { + return x.SetId + } + return "" +} + +func (x *HostSetMember) GetCatalogId() string { + if x != nil { + return x.CatalogId + } + return "" +} + +var File_controller_storage_host_plugin_store_v1_host_proto protoreflect.FileDescriptor + +var file_controller_storage_host_plugin_store_v1_host_proto_rawDesc = []byte{ + 0x0a, 0x32, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f, + 0x72, 0x61, 0x67, 0x65, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, + 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x27, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, + 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x2e, 0x70, 0x6c, + 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x1c, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, + 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2f, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, + 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2a, 0x63, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, + 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9e, 0x03, 0x0a, 0x0b, 0x48, 0x6f, 0x73, + 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, + 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, + 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, + 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, + 0x6d, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, + 0x24, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x10, 0xc2, + 0xdd, 0x29, 0x0c, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x40, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1e, 0xc2, 0xdd, 0x29, 0x1a, + 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, + 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x63, 0x6f, 0x70, 0x65, + 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x49, 0x64, 0x12, + 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x61, + 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x22, 0xb6, 0x05, 0x0a, 0x07, 0x48, 0x6f, + 0x73, 0x74, 0x53, 0x65, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, + 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, + 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, + 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x50, 0x0a, 0x0e, + 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, + 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x52, 0x0c, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x79, 0x6e, 0x63, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1b, + 0x0a, 0x09, 0x6e, 0x65, 0x65, 0x64, 0x5f, 0x73, 0x79, 0x6e, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x08, 0x6e, 0x65, 0x65, 0x64, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x24, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x42, 0x10, 0xc2, 0xdd, 0x29, 0x0c, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x40, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1e, 0xc2, 0xdd, 0x29, 0x1a, 0x0a, 0x0b, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, + 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, + 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, + 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x5e, 0x0a, 0x13, + 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x42, 0x2d, 0xc2, 0xdd, 0x29, 0x29, 0x0a, + 0x12, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x73, 0x12, 0x13, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x5f, 0x65, + 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x52, 0x12, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, + 0x72, 0x65, 0x64, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x64, 0x0a, 0x15, + 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x73, 0x65, + 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x42, 0x30, 0xc2, 0xdd, 0x29, + 0x2c, 0x0a, 0x13, 0x53, 0x79, 0x6e, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x53, + 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x15, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x52, 0x13, 0x73, + 0x79, 0x6e, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x53, 0x65, 0x63, 0x6f, 0x6e, + 0x64, 0x73, 0x22, 0x98, 0x02, 0x0a, 0x11, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, + 0x6f, 0x67, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x61, + 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, + 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, + 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x54, 0x69, 0x6d, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, + 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, + 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x74, 0x5f, + 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x63, 0x74, + 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6b, 0x65, 0x79, 0x49, 0x64, 0x22, 0x8d, 0x03, + 0x0a, 0x04, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, + 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, + 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x12, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, + 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, + 0x67, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, + 0x0c, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x09, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x70, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, + 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x6e, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x0a, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x08, 0x64, 0x6e, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x5e, 0x0a, + 0x0d, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x17, + 0x0a, 0x07, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x68, 0x6f, 0x73, 0x74, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x65, 0x74, 0x49, 0x64, 0x12, 0x1d, + 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x49, 0x64, 0x42, 0x40, 0x5a, + 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, + 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, + 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2f, 0x70, 0x6c, 0x75, + 0x67, 0x69, 0x6e, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_controller_storage_host_plugin_store_v1_host_proto_rawDescOnce sync.Once + file_controller_storage_host_plugin_store_v1_host_proto_rawDescData = file_controller_storage_host_plugin_store_v1_host_proto_rawDesc +) + +func file_controller_storage_host_plugin_store_v1_host_proto_rawDescGZIP() []byte { + file_controller_storage_host_plugin_store_v1_host_proto_rawDescOnce.Do(func() { + file_controller_storage_host_plugin_store_v1_host_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_storage_host_plugin_store_v1_host_proto_rawDescData) + }) + return file_controller_storage_host_plugin_store_v1_host_proto_rawDescData +} + +var file_controller_storage_host_plugin_store_v1_host_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_controller_storage_host_plugin_store_v1_host_proto_goTypes = []interface{}{ + (*HostCatalog)(nil), // 0: controller.storage.host.plugin.store.v1.HostCatalog + (*HostSet)(nil), // 1: controller.storage.host.plugin.store.v1.HostSet + (*HostCatalogSecret)(nil), // 2: controller.storage.host.plugin.store.v1.HostCatalogSecret + (*Host)(nil), // 3: controller.storage.host.plugin.store.v1.Host + (*HostSetMember)(nil), // 4: controller.storage.host.plugin.store.v1.HostSetMember + (*timestamp.Timestamp)(nil), // 5: controller.storage.timestamp.v1.Timestamp +} +var file_controller_storage_host_plugin_store_v1_host_proto_depIdxs = []int32{ + 5, // 0: controller.storage.host.plugin.store.v1.HostCatalog.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 1: controller.storage.host.plugin.store.v1.HostCatalog.update_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 2: controller.storage.host.plugin.store.v1.HostSet.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 3: controller.storage.host.plugin.store.v1.HostSet.update_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 4: controller.storage.host.plugin.store.v1.HostSet.last_sync_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 5: controller.storage.host.plugin.store.v1.HostCatalogSecret.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 6: controller.storage.host.plugin.store.v1.HostCatalogSecret.update_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 7: controller.storage.host.plugin.store.v1.Host.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 8: controller.storage.host.plugin.store.v1.Host.update_time:type_name -> controller.storage.timestamp.v1.Timestamp + 9, // [9:9] is the sub-list for method output_type + 9, // [9:9] is the sub-list for method input_type + 9, // [9:9] is the sub-list for extension type_name + 9, // [9:9] is the sub-list for extension extendee + 0, // [0:9] is the sub-list for field type_name +} + +func init() { file_controller_storage_host_plugin_store_v1_host_proto_init() } +func file_controller_storage_host_plugin_store_v1_host_proto_init() { + if File_controller_storage_host_plugin_store_v1_host_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HostCatalog); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HostSet); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HostCatalogSecret); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Host); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HostSetMember); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_controller_storage_host_plugin_store_v1_host_proto_rawDesc, + NumEnums: 0, + NumMessages: 5, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_controller_storage_host_plugin_store_v1_host_proto_goTypes, + DependencyIndexes: file_controller_storage_host_plugin_store_v1_host_proto_depIdxs, + MessageInfos: file_controller_storage_host_plugin_store_v1_host_proto_msgTypes, + }.Build() + File_controller_storage_host_plugin_store_v1_host_proto = out.File + file_controller_storage_host_plugin_store_v1_host_proto_rawDesc = nil + file_controller_storage_host_plugin_store_v1_host_proto_goTypes = nil + file_controller_storage_host_plugin_store_v1_host_proto_depIdxs = nil +} diff --git a/internal/host/plugin/testing.go b/internal/host/plugin/testing.go new file mode 100644 index 000000000..6f6b2a62f --- /dev/null +++ b/internal/host/plugin/testing.go @@ -0,0 +1,292 @@ +package plugin + +import ( + "context" + "crypto/rand" + "fmt" + "io" + "net" + "sort" + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/host" + "github.com/hashicorp/boundary/internal/host/plugin/store" + "github.com/hashicorp/boundary/internal/kms" + hostplugin "github.com/hashicorp/boundary/internal/plugin/host" + "github.com/hashicorp/boundary/internal/scheduler" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" + "github.com/hashicorp/go-secure-stdlib/base62" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCatalogs creates count number of static host catalogs to the provided DB +// with the provided scope id. If any errors are encountered during the creation of +// the host catalog, the test will fail. +func TestCatalogs(t *testing.T, conn *db.DB, scopeId, pluginId string, count int) []*HostCatalog { + t.Helper() + var cats []*HostCatalog + for i := 0; i < count; i++ { + cats = append(cats, TestCatalog(t, conn, scopeId, pluginId)) + } + return cats +} + +// TestCatalog creates a plugin host catalogs to the provided DB +// with the provided scope id. If any errors are encountered during the creation of +// the host catalog, the test will fail. +func TestCatalog(t *testing.T, conn *db.DB, scopeId, pluginId string, opt ...Option) *HostCatalog { + t.Helper() + ctx := context.Background() + w := db.New(conn) + + cat, err := NewHostCatalog(ctx, scopeId, pluginId, opt...) + require.NoError(t, err) + assert.NotNil(t, cat) + + plg := hostplugin.NewPlugin() + plg.PublicId = pluginId + require.NoError(t, w.LookupByPublicId(ctx, plg)) + + id, err := newHostCatalogId(ctx) + assert.NoError(t, err) + assert.NotEmpty(t, id) + cat.PublicId = id + + require.NoError(t, w.Create(ctx, cat)) + return cat +} + +// TestSet creates a plugin host sets in the provided DB +// with the provided catalog id. The catalog must have been created +// previously. The test will fail if any errors are encountered. +func TestSet(t *testing.T, conn *db.DB, kmsCache *kms.Kms, sched *scheduler.Scheduler, hc *HostCatalog, plgm map[string]plgpb.HostPluginServiceClient, opt ...Option) *HostSet { + t.Helper() + require := require.New(t) + ctx := context.Background() + rw := db.New(conn) + + repo, err := NewRepository(rw, rw, kmsCache, sched, plgm) + require.NoError(err) + + set, err := NewHostSet(ctx, hc.PublicId, opt...) + require.NoError(err) + require.NotNil(set) + + plg := hostplugin.NewPlugin() + plg.PublicId = hc.GetPluginId() + require.NoError(rw.LookupByPublicId(ctx, plg)) + + id, err := newHostSetId(ctx) + require.NoError(err) + require.NotEmpty(id) + + set, _, err = repo.CreateSet(ctx, hc.ScopeId, set, opt...) + require.NoError(err) + + return set +} + +// TestSetMembers adds hosts to the specified setId in the provided DB. +// The set and hosts must have been created previously and belong to the +// same catalog. The test will fail if any errors are encountered. +func TestSetMembers(t *testing.T, conn *db.DB, setId string, hosts []*Host) []*HostSetMember { + t.Helper() + assert := assert.New(t) + + var members []*HostSetMember + for _, host := range hosts { + member, err := NewHostSetMember(context.Background(), setId, host.PublicId) + assert.NoError(err) + assert.NotNil(member) + + w := db.New(conn) + err2 := w.Create(context.Background(), member) + assert.NoError(err2) + members = append(members, member) + } + return members +} + +// TestHost creates a plugin host in the provided DB in the catalog with the +// provided catalog id. The catalog must have been created previously. +// The test will fail if any errors are encountered. +func TestHost(t *testing.T, conn *db.DB, catId, externId string, opt ...Option) *Host { + t.Helper() + w := db.New(conn) + ctx := context.Background() + host1 := NewHost(ctx, catId, externId, opt...) + var err error + host1.PublicId, err = newHostId(ctx, catId, externId) + require.NoError(t, err) + require.NoError(t, w.Create(ctx, host1)) + + var ipAddresses []interface{} + if len(host1.GetIpAddresses()) > 0 { + sort.Strings(host1.IpAddresses) + ipAddresses = make([]interface{}, 0, len(host1.GetIpAddresses())) + for _, a := range host1.GetIpAddresses() { + obj, err := host.NewIpAddress(ctx, host1.PublicId, a) + require.NoError(t, err) + ipAddresses = append(ipAddresses, obj) + } + require.NoError(t, w.CreateItems(ctx, ipAddresses)) + } + + var dnsNames []interface{} + if len(host1.GetDnsNames()) > 0 { + sort.Strings(host1.DnsNames) + dnsNames = make([]interface{}, 0, len(host1.GetDnsNames())) + for _, n := range host1.GetDnsNames() { + obj, err := host.NewDnsName(ctx, host1.PublicId, n) + require.NoError(t, err) + dnsNames = append(dnsNames, obj) + } + require.NoError(t, w.CreateItems(ctx, dnsNames)) + } + return host1 +} + +func TestExternalHosts(t *testing.T, catalog *HostCatalog, setIds []string, count int) ([]*plgpb.ListHostsResponseHost, []*Host) { + t.Helper() + require := require.New(t) + retRH := make([]*plgpb.ListHostsResponseHost, 0, count) + retH := make([]*Host, 0, count) + if setIds == nil { + // Prevent panics + setIds = make([]string, 0) + } + + for i := 0; i < count; i++ { + externalId, err := base62.Random(10) + require.NoError(err) + + ipStr := testGetIpAddress(t) + dnsName := testGetDnsName(t) + + rh := &plgpb.ListHostsResponseHost{ + ExternalId: externalId, + Name: base62.MustRandom(10), + Description: base62.MustRandom(10), + SetIds: setIds[0 : i+1], + IpAddresses: []string{ipStr}, + DnsNames: []string{dnsName}, + } + retRH = append(retRH, rh) + + publicId, err := newHostId(context.Background(), catalog.PublicId, externalId) + require.NoError(err) + + retH = append(retH, &Host{ + PluginId: catalog.PluginId, + SetIds: setIds[0 : i+1], + Host: &store.Host{ + Name: rh.Name, + Description: rh.Description, + CatalogId: catalog.PublicId, + PublicId: publicId, + ExternalId: externalId, + IpAddresses: []string{ipStr}, + DnsNames: []string{dnsName}, + }, + }) + } + + return retRH, retH +} + +// TestRunSetSync runs the set sync job a single time. +func TestRunSetSync(t *testing.T, conn *db.DB, kmsCache *kms.Kms, plgm map[string]plgpb.HostPluginServiceClient) { + t.Helper() + rw := db.New(conn) + ctx := context.Background() + + j, err := newSetSyncJob(ctx, rw, rw, kmsCache, plgm) + require.NoError(t, err) + require.NoError(t, j.Run(ctx)) +} + +func testGetDnsName(t *testing.T) string { + dnsName, err := base62.Random(10) + require.NoError(t, err) + return fmt.Sprintf("%s.example.com", dnsName) +} + +func testGetIpAddress(t *testing.T) string { + ipBytes := make([]byte, 4) + for { + lr := io.LimitReader(rand.Reader, 4) + n, err := lr.Read(ipBytes) + require.NoError(t, err) + require.Equal(t, n, 4) + ip := net.IP(ipBytes) + v4 := ip.To4() + if v4 != nil { + return v4.String() + } + } +} + +var _ plgpb.HostPluginServiceServer = (*TestPluginServer)(nil) + +// TestPluginServer provides a host plugin service server where each method can be overwritten for tests. +type TestPluginServer struct { + OnCreateCatalogFn func(context.Context, *plgpb.OnCreateCatalogRequest) (*plgpb.OnCreateCatalogResponse, error) + OnUpdateCatalogFn func(context.Context, *plgpb.OnUpdateCatalogRequest) (*plgpb.OnUpdateCatalogResponse, error) + OnDeleteCatalogFn func(context.Context, *plgpb.OnDeleteCatalogRequest) (*plgpb.OnDeleteCatalogResponse, error) + OnCreateSetFn func(context.Context, *plgpb.OnCreateSetRequest) (*plgpb.OnCreateSetResponse, error) + OnUpdateSetFn func(context.Context, *plgpb.OnUpdateSetRequest) (*plgpb.OnUpdateSetResponse, error) + OnDeleteSetFn func(context.Context, *plgpb.OnDeleteSetRequest) (*plgpb.OnDeleteSetResponse, error) + ListHostsFn func(context.Context, *plgpb.ListHostsRequest) (*plgpb.ListHostsResponse, error) + plgpb.UnimplementedHostPluginServiceServer +} + +func (t TestPluginServer) OnCreateCatalog(ctx context.Context, req *plgpb.OnCreateCatalogRequest) (*plgpb.OnCreateCatalogResponse, error) { + if t.OnCreateCatalogFn == nil { + return t.UnimplementedHostPluginServiceServer.OnCreateCatalog(ctx, req) + } + return t.OnCreateCatalogFn(ctx, req) +} + +func (t TestPluginServer) OnUpdateCatalog(ctx context.Context, req *plgpb.OnUpdateCatalogRequest) (*plgpb.OnUpdateCatalogResponse, error) { + if t.OnUpdateCatalogFn == nil { + return t.UnimplementedHostPluginServiceServer.OnUpdateCatalog(ctx, req) + } + return t.OnUpdateCatalogFn(ctx, req) +} + +func (t TestPluginServer) OnDeleteCatalog(ctx context.Context, req *plgpb.OnDeleteCatalogRequest) (*plgpb.OnDeleteCatalogResponse, error) { + if t.OnDeleteCatalogFn == nil { + return t.UnimplementedHostPluginServiceServer.OnDeleteCatalog(ctx, req) + } + return t.OnDeleteCatalogFn(ctx, req) +} + +func (t TestPluginServer) OnCreateSet(ctx context.Context, req *plgpb.OnCreateSetRequest) (*plgpb.OnCreateSetResponse, error) { + if t.OnCreateSetFn == nil { + return t.UnimplementedHostPluginServiceServer.OnCreateSet(ctx, req) + } + return t.OnCreateSetFn(ctx, req) +} + +func (t TestPluginServer) OnUpdateSet(ctx context.Context, req *plgpb.OnUpdateSetRequest) (*plgpb.OnUpdateSetResponse, error) { + if t.OnUpdateSetFn == nil { + return t.UnimplementedHostPluginServiceServer.OnUpdateSet(ctx, req) + } + return t.OnUpdateSetFn(ctx, req) +} + +func (t TestPluginServer) OnDeleteSet(ctx context.Context, req *plgpb.OnDeleteSetRequest) (*plgpb.OnDeleteSetResponse, error) { + if t.OnDeleteSetFn == nil { + return t.UnimplementedHostPluginServiceServer.OnDeleteSet(ctx, req) + } + return t.OnDeleteSetFn(ctx, req) +} + +func (t TestPluginServer) ListHosts(ctx context.Context, req *plgpb.ListHostsRequest) (*plgpb.ListHostsResponse, error) { + if t.ListHostsFn == nil { + return t.UnimplementedHostPluginServiceServer.ListHosts(ctx, req) + } + return t.ListHostsFn(ctx, req) +} diff --git a/internal/host/plugin/testing_test.go b/internal/host/plugin/testing_test.go new file mode 100644 index 000000000..3b75a30b8 --- /dev/null +++ b/internal/host/plugin/testing_test.go @@ -0,0 +1,142 @@ +package plugin + +import ( + "context" + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/plugin/host" + "github.com/hashicorp/boundary/internal/scheduler" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_TestCatalogs(t *testing.T) { + assert, require := assert.New(t), require.New(t) + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + _, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + require.NotNil(proj) + assert.NotEmpty(proj.GetPublicId()) + + plg := host.TestPlugin(t, conn, "test") + require.NotNil(plg) + assert.NotEmpty(plg.GetPublicId()) + + cs := TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId(), WithName("foo"), WithDescription("bar")) + assert.NotEmpty(cs.GetPublicId()) + db.AssertPublicId(t, HostCatalogPrefix, cs.GetPublicId()) + assert.Equal("foo", cs.GetName()) + assert.Equal("bar", cs.GetDescription()) +} + +func Test_TestSet(t *testing.T) { + assert, require := assert.New(t), require.New(t) + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + sched := scheduler.TestScheduler(t, conn, wrapper) + kmsCache := kms.TestKms(t, conn, wrapper) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + require.NotNil(prj) + assert.NotEmpty(prj.GetPublicId()) + + plg := host.TestPlugin(t, conn, "test") + require.NotNil(plg) + assert.NotEmpty(plg.GetPublicId()) + + c := TestCatalog(t, conn, prj.GetPublicId(), plg.GetPublicId()) + set := TestSet(t, conn, kmsCache, sched, c, map[string]plgpb.HostPluginServiceClient{plg.GetPublicId(): NewWrappingPluginClient(&TestPluginServer{})}, WithName("foo"), WithDescription("bar")) + assert.NotEmpty(set.GetPublicId()) + db.AssertPublicId(t, HostSetPrefix, set.GetPublicId()) + assert.Equal("foo", set.GetName()) + assert.Equal("bar", set.GetDescription()) +} + +func Test_TestHosts(t *testing.T) { + assert, require := assert.New(t), require.New(t) + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + + plg := host.TestPlugin(t, conn, "test") + require.NotNil(plg) + assert.NotEmpty(plg.GetPublicId()) + + require.NotNil(org) + assert.NotEmpty(org.GetPublicId()) + + c := TestCatalog(t, conn, org.GetPublicId(), plg.GetPublicId()) + + h := TestHost(t, conn, c.GetPublicId(), plg.GetPublicId()) + assert.NotEmpty(h.GetPublicId()) +} + +func Test_TestSetMembers(t *testing.T) { + assert, require := assert.New(t), require.New(t) + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + sched := scheduler.TestScheduler(t, conn, wrapper) + kmsCache := kms.TestKms(t, conn, wrapper) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + require.NotNil(prj) + assert.NotEmpty(prj.GetPublicId()) + + plg := host.TestPlugin(t, conn, "test") + require.NotNil(plg) + assert.NotEmpty(plg.GetPublicId()) + + c := TestCatalog(t, conn, prj.GetPublicId(), plg.GetPublicId()) + s := TestSet(t, conn, kmsCache, sched, c, map[string]plgpb.HostPluginServiceClient{plg.GetPublicId(): NewWrappingPluginClient(&TestPluginServer{})}) + + h := TestHost(t, conn, c.GetPublicId(), plg.GetPublicId()) + members := TestSetMembers(t, conn, s.PublicId, []*Host{h}) + assert.Len(members, 1) + assert.Equal(h.GetPublicId(), members[0].GetHostId()) + assert.Equal(s.GetPublicId(), members[0].GetSetId()) +} + +func Test_TestRunSetSync(t *testing.T) { + assert, require := assert.New(t), require.New(t) + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + sched := scheduler.TestScheduler(t, conn, wrapper) + kmsCache := kms.TestKms(t, conn, wrapper) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + require.NotNil(prj) + assert.NotEmpty(prj.GetPublicId()) + + plg := host.TestPlugin(t, conn, "test") + require.NotNil(plg) + assert.NotEmpty(plg.GetPublicId()) + pluginServer := &TestPluginServer{} + plgm := map[string]plgpb.HostPluginServiceClient{plg.GetPublicId(): NewWrappingPluginClient(pluginServer)} + + c := TestCatalog(t, conn, prj.GetPublicId(), plg.GetPublicId()) + s1 := TestSet(t, conn, kmsCache, sched, c, plgm) + s2 := TestSet(t, conn, kmsCache, sched, c, plgm) + + pluginServer.ListHostsFn = func(ctx context.Context, req *plgpb.ListHostsRequest) (*plgpb.ListHostsResponse, error) { + var setIds []string + for _, s := range req.GetSets() { + setIds = append(setIds, s.GetId()) + } + resp := &plgpb.ListHostsResponse{Hosts: []*plgpb.ListHostsResponseHost{ + { + ExternalId: "test", + SetIds: setIds, + IpAddresses: []string{"10.0.0.1"}, + }, + }} + return resp, nil + } + + TestRunSetSync(t, conn, kmsCache, plgm) + rw := db.New(conn) + var ha []*hostAgg + require.NoError(rw.SearchWhere(context.Background(), &ha, "true", nil)) + require.Len(ha, 1) + assert.ElementsMatch(ha[0].toHost().SetIds, []string{s1.GetPublicId(), s2.GetPublicId()}) +} diff --git a/internal/host/preferred_endpoint.go b/internal/host/preferred_endpoint.go new file mode 100644 index 000000000..621448d85 --- /dev/null +++ b/internal/host/preferred_endpoint.go @@ -0,0 +1,87 @@ +package host + +import ( + "context" + + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/host/store" + "github.com/hashicorp/boundary/internal/libs/endpoint" + "google.golang.org/protobuf/proto" +) + +// defaultPreferredEndpointTableName defines the default table name for a +// PreferredEndpoint +const defaultPreferredEndpointTableName = "host_set_preferred_endpoint" + +// PreferredEndpoint defines an endpoint condition for a host set. +// PreferredEndpoints are value objects of an HostSet, therefore there's no need +// for oplog metadata, since only the HostSet will have metadata because it's +// the root aggregate. +type PreferredEndpoint struct { + *store.PreferredEndpoint + tableName string +} + +// NewPreferredEndpoint creates a new in memory preferred endpoint assigned to a +// HostSet. It supports no options. +func NewPreferredEndpoint(ctx context.Context, hostSetId string, priority uint32, condition string) (*PreferredEndpoint, error) { + const op = "host.NewPreferredEndpoint" + + pe := &PreferredEndpoint{ + PreferredEndpoint: &store.PreferredEndpoint{ + HostSetId: hostSetId, + Priority: priority, + Condition: condition, + }, + } + if err := pe.validate(ctx, op); err != nil { + return nil, err // intentionally not wrapped + } + return pe, nil +} + +// validate the PreferredEndpoint. On success, it will return nil. +func (pe *PreferredEndpoint) validate(ctx context.Context, caller errors.Op) error { + if pe.HostSetId == "" { + return errors.New(ctx, errors.InvalidParameter, caller, "missing host set id") + } + if pe.Priority < 1 { + return errors.New(ctx, errors.InvalidParameter, caller, "invalid priority value") + } + if pe.Condition == "" { + return errors.New(ctx, errors.InvalidParameter, caller, "missing condition") + } + if _, err := endpoint.NewPreferencer(ctx, endpoint.WithPreferenceOrder([]string{pe.Condition})); err != nil { + return errors.Wrap(ctx, err, caller) + } + + return nil +} + +// AllocPreferredEndpoint makes an empty one in-memory +func AllocPreferredEndpoint() *PreferredEndpoint { + return &PreferredEndpoint{ + PreferredEndpoint: &store.PreferredEndpoint{}, + } +} + +// Clone a PreferredEndpoint +func (c *PreferredEndpoint) Clone() *PreferredEndpoint { + cp := proto.Clone(c.PreferredEndpoint) + return &PreferredEndpoint{ + PreferredEndpoint: cp.(*store.PreferredEndpoint), + } +} + +// TableName returns the table name. +func (c *PreferredEndpoint) TableName() string { + if c.tableName != "" { + return c.tableName + } + return defaultPreferredEndpointTableName +} + +// SetTableName sets the table name. +func (c *PreferredEndpoint) SetTableName(n string) { + c.tableName = n +} diff --git a/internal/host/preferred_endpoint_test.go b/internal/host/preferred_endpoint_test.go new file mode 100644 index 000000000..a4a6a6ff9 --- /dev/null +++ b/internal/host/preferred_endpoint_test.go @@ -0,0 +1,227 @@ +package host_test + +import ( + "context" + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/host" + "github.com/hashicorp/boundary/internal/host/plugin" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + hostplg "github.com/hashicorp/boundary/internal/plugin/host" + "github.com/hashicorp/boundary/internal/scheduler" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPreferredEndpoint_Create(t *testing.T) { + t.Parallel() + ctx := context.TODO() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + sched := scheduler.TestScheduler(t, conn, wrapper) + rw := db.New(conn) + kmsCache := kms.TestKms(t, conn, wrapper) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + plg := hostplg.TestPlugin(t, conn, "create") + catalog := plugin.TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + set := plugin.TestSet(t, conn, kmsCache, sched, catalog, map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): plugin.NewWrappingPluginClient(&plugin.TestPluginServer{}), + }) + + type args struct { + hostSetId string + priority uint32 + condition string + } + tests := []struct { + name string + args args + want *host.PreferredEndpoint + wantErr bool + wantIsErr errors.Code + create bool + wantCreateErr bool + wantCreateIsErr errors.Code + }{ + { + name: "valid", + args: args{ + hostSetId: set.PublicId, + priority: 1, + condition: "cidr:1.2.3.4", + }, + create: true, + want: func() *host.PreferredEndpoint { + want := host.AllocPreferredEndpoint() + want.HostSetId = set.PublicId + want.Condition = "cidr:1.2.3.4" + want.Priority = 1 + return want + }(), + }, + { + name: "dup", // must follow "valid" test. Url must be be unique for an OidcMethodId + args: args{ + hostSetId: set.PublicId, + priority: 1, + condition: "cidr:1.2.3.4", + }, + create: true, + want: func() *host.PreferredEndpoint { + want := host.AllocPreferredEndpoint() + want.HostSetId = set.PublicId + want.Condition = "cidr:1.2.3.4" + want.Priority = 1 + return want + }(), + wantCreateErr: true, + wantCreateIsErr: errors.NotUnique, + }, + { + name: "bad-condition", + args: args{ + hostSetId: set.PublicId, + priority: 1, + condition: "foobar:1.2.3.4", + }, + wantErr: true, + wantIsErr: errors.InvalidParameter, + }, + { + name: "empty-host-set", + args: args{ + hostSetId: "", + }, + wantErr: true, + wantIsErr: errors.InvalidParameter, + }, + { + name: "invalid-priority", + args: args{ + hostSetId: set.PublicId, + priority: 0, + }, + wantErr: true, + wantIsErr: errors.InvalidParameter, + }, + { + name: "empty-condition", + args: args{ + hostSetId: set.PublicId, + priority: 1, + }, + wantErr: true, + wantIsErr: errors.InvalidParameter, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + got, err := host.NewPreferredEndpoint(ctx, tt.args.hostSetId, tt.args.priority, tt.args.condition) + if tt.wantErr { + require.Error(err) + assert.True(errors.Match(errors.T(tt.wantIsErr), err)) + return + } + require.NoError(err) + assert.Equal(tt.want, got) + if tt.create { + ctx := context.Background() + err = rw.Create(ctx, got) + if tt.wantCreateErr { + assert.Error(err) + assert.True(errors.Match(errors.T(tt.wantCreateIsErr), err)) + return + } else { + assert.NoError(err) + } + found := host.AllocPreferredEndpoint() + require.NoError(rw.LookupWhere(ctx, found, "host_set_id = ? and priority = ?", tt.args.hostSetId, tt.args.priority)) + assert.Equal(got, found) + } + }) + } +} + +func TestPreferredEndpoint_Delete(t *testing.T) { + t.Parallel() + ctx := context.TODO() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + sched := scheduler.TestScheduler(t, conn, wrapper) + rw := db.New(conn) + kmsCache := kms.TestKms(t, conn, wrapper) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + plg := hostplg.TestPlugin(t, conn, "create") + catalog := plugin.TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + set := plugin.TestSet(t, conn, kmsCache, sched, catalog, map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): plugin.NewWrappingPluginClient(&plgpb.UnimplementedHostPluginServiceServer{}), + }) + + peFunc := func(priority uint32, condition string) *host.PreferredEndpoint { + ep, err := host.NewPreferredEndpoint(ctx, set.PublicId, priority, condition) + require.NoError(t, err) + return ep + } + + tests := []struct { + pes []*host.PreferredEndpoint + name string + overrides func(*host.PreferredEndpoint) + wantErr bool + wantRowsDeleted int + }{ + { + name: "valid-single", + pes: []*host.PreferredEndpoint{peFunc(1, "cidr:1.2.3.4")}, + wantErr: false, + wantRowsDeleted: 1, + }, + { + name: "bad-host-set-id", + pes: []*host.PreferredEndpoint{peFunc(1, "cidr:1.2.3.4")}, + overrides: func(pe *host.PreferredEndpoint) { pe.HostSetId = "bad-id" }, + wantErr: false, + wantRowsDeleted: 0, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + var eput *host.PreferredEndpoint + for _, pe := range tt.pes { + cloned := pe.Clone() + if eput == nil { + eput = cloned + } + require.NoError(rw.Create(ctx, &cloned)) + } + if tt.overrides != nil { + tt.overrides(eput) + } + + deletedRows, err := rw.Delete(ctx, &eput, db.WithWhere("host_set_id = ?", eput.HostSetId)) + if tt.wantErr { + require.Error(err) + return + } + + require.NoError(err) + if tt.wantRowsDeleted == 0 { + assert.Equal(tt.wantRowsDeleted, deletedRows) + return + } + require.Equal(tt.wantRowsDeleted, deletedRows) + found := host.AllocPreferredEndpoint() + err = rw.LookupWhere(ctx, &found, "host_set_id = ?", set.PublicId) + assert.True(errors.IsNotFoundError(err)) + }) + } +} diff --git a/internal/host/static/host.go b/internal/host/static/host.go index b37341f9e..b367111b3 100644 --- a/internal/host/static/host.go +++ b/internal/host/static/host.go @@ -38,6 +38,16 @@ func NewHost(catalogId string, opt ...Option) (*Host, error) { return host, nil } +// For compatibility with the general Host type +func (h *Host) GetIpAddresses() []string { + return nil +} + +// For compatibility with the general Host type +func (h *Host) GetDnsNames() []string { + return nil +} + // TableName returns the table name for the host. func (h *Host) TableName() string { if h.tableName != "" { diff --git a/internal/host/static/repository_host_set.go b/internal/host/static/repository_host_set.go index a3d986924..b28138f4c 100644 --- a/internal/host/static/repository_host_set.go +++ b/internal/host/static/repository_host_set.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/boundary/internal/db" dbcommon "github.com/hashicorp/boundary/internal/db/common" "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/host" "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/oplog" ) @@ -182,6 +183,26 @@ func (r *Repository) UpdateSet(ctx context.Context, scopeId string, s *HostSet, return returnedHostSet, hosts, rowsUpdated, nil } +// Endpoints returns a slice of host.Endpoint for the provided set id. +// If there are no hosts in the provided set id the slice is empty. +// If the set does not exist an error is returned. +func (r *Repository) Endpoints(ctx context.Context, setId string) ([]*host.Endpoint, error) { + const op = "static.(Repository).Endpoints" + _, hs, err := r.lookupSet(ctx, setId) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + var es []*host.Endpoint + for _, h := range hs { + es = append(es, &host.Endpoint{ + HostId: h.GetPublicId(), + SetId: setId, + Address: h.GetAddress(), + }) + } + return es, err +} + // LookupSet will look up a host set in the repository and return the host // set and the hosts assigned to the host set. If the host set is not // found, it will return nil, nil, nil. The WithLimit option can be used to @@ -191,6 +212,11 @@ func (r *Repository) LookupSet(ctx context.Context, publicId string, opt ...Opti if publicId == "" { return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "no public id") } + return r.lookupSet(ctx, publicId, opt...) +} + +func (r *Repository) lookupSet(ctx context.Context, publicId string, opt ...Option) (*HostSet, []*Host, error) { + const op = "static.(Repository).lookupSet" opts := getOpts(opt...) limit := r.defaultLimit if opts.withLimit != 0 { @@ -201,29 +227,20 @@ func (r *Repository) LookupSet(ctx context.Context, publicId string, opt ...Opti s := allocHostSet() s.PublicId = publicId + if err := r.reader.LookupByPublicId(ctx, s); err != nil { + if errors.IsNotFoundError(err) { + return nil, nil, nil + } + return nil, nil, errors.Wrap(ctx, err, op) + } var hosts []*Host - _, err := r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, func(reader db.Reader, _ db.Writer) error { - if err := reader.LookupByPublicId(ctx, s); err != nil { - if errors.IsNotFoundError(err) { - s = nil - return nil - } - if err != nil { - return errors.Wrap(ctx, err, op) - } - return nil - } - var err error - hosts, err = getHosts(ctx, reader, s.PublicId, limit) - if err != nil { - return errors.Wrap(ctx, err, op) - } - return nil - }) + var err error + if hosts, err = getHosts(ctx, r.reader, s.PublicId, limit); err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } if err != nil { return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in %s", s.PublicId))) } - return s, hosts, nil } diff --git a/internal/host/static/repository_host_set_member.go b/internal/host/static/repository_host_set_member.go index dead39500..76b25fb99 100644 --- a/internal/host/static/repository_host_set_member.go +++ b/internal/host/static/repository_host_set_member.go @@ -100,8 +100,11 @@ func updateVersion(ctx context.Context, w db.Writer, wrapper wrapping.Wrapper, m switch { case err != nil: return errors.Wrap(ctx, err, op) + case rowsUpdated == 0: + return errors.New(ctx, errors.RecordNotFound, op, "no matching version for host set found") case rowsUpdated > 1: return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been updated") + } msgs = append(msgs, setMsg) diff --git a/internal/host/static/store/static.pb.go b/internal/host/static/store/static.pb.go index 6388b485b..f0ed54bb3 100644 --- a/internal/host/static/store/static.pb.go +++ b/internal/host/static/store/static.pb.go @@ -433,6 +433,64 @@ func (x *HostSetMember) GetCatalogId() string { return "" } +// These fields are not implemented yet on the host set. They are captured +// here for the purpose of identifying the mask maps which are on the top level +// api set resource but aren't present in the static host set storage. +type UnimplementedSetFields struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PreferredEndpoints []string `protobuf:"bytes,11,rep,name=preferred_endpoints,json=preferredEndpoints,proto3" json:"preferred_endpoints,omitempty"` + SyncIntervalSeconds int32 `protobuf:"varint,22,opt,name=sync_interval_seconds,json=syncIntervalSeconds,proto3" json:"sync_interval_seconds,omitempty"` +} + +func (x *UnimplementedSetFields) Reset() { + *x = UnimplementedSetFields{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_host_static_store_v1_static_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UnimplementedSetFields) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UnimplementedSetFields) ProtoMessage() {} + +func (x *UnimplementedSetFields) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_host_static_store_v1_static_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UnimplementedSetFields.ProtoReflect.Descriptor instead. +func (*UnimplementedSetFields) Descriptor() ([]byte, []int) { + return file_controller_storage_host_static_store_v1_static_proto_rawDescGZIP(), []int{4} +} + +func (x *UnimplementedSetFields) GetPreferredEndpoints() []string { + if x != nil { + return x.PreferredEndpoints + } + return nil +} + +func (x *UnimplementedSetFields) GetSyncIntervalSeconds() int32 { + if x != nil { + return x.SyncIntervalSeconds + } + return 0 +} + var File_controller_storage_host_static_store_v1_static_proto protoreflect.FileDescriptor var file_controller_storage_host_static_store_v1_static_proto_rawDesc = []byte{ @@ -524,11 +582,25 @@ var file_controller_storage_host_static_store_v1_static_proto_rawDesc = []byte{ 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x65, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, - 0x49, 0x64, 0x42, 0x40, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, - 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x68, 0x6f, 0x73, - 0x74, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, - 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x49, 0x64, 0x22, 0xde, 0x01, 0x0a, 0x16, 0x55, 0x6e, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x65, 0x64, 0x53, 0x65, 0x74, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x5e, 0x0a, + 0x13, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x42, 0x2d, 0xc2, 0xdd, 0x29, 0x29, + 0x0a, 0x12, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x45, 0x6e, 0x64, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x73, 0x12, 0x13, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x5f, + 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x52, 0x12, 0x70, 0x72, 0x65, 0x66, 0x65, + 0x72, 0x72, 0x65, 0x64, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x64, 0x0a, + 0x15, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x73, + 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x16, 0x20, 0x01, 0x28, 0x05, 0x42, 0x30, 0xc2, 0xdd, + 0x29, 0x2c, 0x0a, 0x13, 0x53, 0x79, 0x6e, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, + 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x15, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x52, 0x13, + 0x73, 0x79, 0x6e, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x53, 0x65, 0x63, 0x6f, + 0x6e, 0x64, 0x73, 0x42, 0x40, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, + 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x68, 0x6f, + 0x73, 0x74, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, + 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -543,21 +615,22 @@ func file_controller_storage_host_static_store_v1_static_proto_rawDescGZIP() []b return file_controller_storage_host_static_store_v1_static_proto_rawDescData } -var file_controller_storage_host_static_store_v1_static_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_controller_storage_host_static_store_v1_static_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_controller_storage_host_static_store_v1_static_proto_goTypes = []interface{}{ - (*HostCatalog)(nil), // 0: controller.storage.host.static.store.v1.HostCatalog - (*Host)(nil), // 1: controller.storage.host.static.store.v1.Host - (*HostSet)(nil), // 2: controller.storage.host.static.store.v1.HostSet - (*HostSetMember)(nil), // 3: controller.storage.host.static.store.v1.HostSetMember - (*timestamp.Timestamp)(nil), // 4: controller.storage.timestamp.v1.Timestamp + (*HostCatalog)(nil), // 0: controller.storage.host.static.store.v1.HostCatalog + (*Host)(nil), // 1: controller.storage.host.static.store.v1.Host + (*HostSet)(nil), // 2: controller.storage.host.static.store.v1.HostSet + (*HostSetMember)(nil), // 3: controller.storage.host.static.store.v1.HostSetMember + (*UnimplementedSetFields)(nil), // 4: controller.storage.host.static.store.v1.UnimplementedSetFields + (*timestamp.Timestamp)(nil), // 5: controller.storage.timestamp.v1.Timestamp } var file_controller_storage_host_static_store_v1_static_proto_depIdxs = []int32{ - 4, // 0: controller.storage.host.static.store.v1.HostCatalog.create_time:type_name -> controller.storage.timestamp.v1.Timestamp - 4, // 1: controller.storage.host.static.store.v1.HostCatalog.update_time:type_name -> controller.storage.timestamp.v1.Timestamp - 4, // 2: controller.storage.host.static.store.v1.Host.create_time:type_name -> controller.storage.timestamp.v1.Timestamp - 4, // 3: controller.storage.host.static.store.v1.Host.update_time:type_name -> controller.storage.timestamp.v1.Timestamp - 4, // 4: controller.storage.host.static.store.v1.HostSet.create_time:type_name -> controller.storage.timestamp.v1.Timestamp - 4, // 5: controller.storage.host.static.store.v1.HostSet.update_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 0: controller.storage.host.static.store.v1.HostCatalog.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 1: controller.storage.host.static.store.v1.HostCatalog.update_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 2: controller.storage.host.static.store.v1.Host.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 3: controller.storage.host.static.store.v1.Host.update_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 4: controller.storage.host.static.store.v1.HostSet.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 5: controller.storage.host.static.store.v1.HostSet.update_time:type_name -> controller.storage.timestamp.v1.Timestamp 6, // [6:6] is the sub-list for method output_type 6, // [6:6] is the sub-list for method input_type 6, // [6:6] is the sub-list for extension type_name @@ -619,6 +692,18 @@ func file_controller_storage_host_static_store_v1_static_proto_init() { return nil } } + file_controller_storage_host_static_store_v1_static_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UnimplementedSetFields); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -626,7 +711,7 @@ func file_controller_storage_host_static_store_v1_static_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_controller_storage_host_static_store_v1_static_proto_rawDesc, NumEnums: 0, - NumMessages: 4, + NumMessages: 5, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/host/store/host.pb.go b/internal/host/store/host.pb.go index 25d7e3b0e..2434f98a6 100644 --- a/internal/host/store/host.pb.go +++ b/internal/host/store/host.pb.go @@ -199,6 +199,189 @@ func (x *Set) GetCatalogId() string { return "" } +type PreferredEndpoint struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The ID of the host set this belongs to. + // @inject_tag: `gorm:"primary_key"` + HostSetId string `protobuf:"bytes,1,opt,name=host_set_id,json=hostSetId,proto3" json:"host_set_id,omitempty" gorm:"primary_key"` + // The priority order, zero-indexed + // @inject_tag: `gorm:"primary_key"` + Priority uint32 `protobuf:"varint,2,opt,name=priority,proto3" json:"priority,omitempty" gorm:"primary_key"` + // The string text of the preference condition + // @inject_tag: `gorm:"not_null"` + Condition string `protobuf:"bytes,3,opt,name=condition,proto3" json:"condition,omitempty" gorm:"not_null"` +} + +func (x *PreferredEndpoint) Reset() { + *x = PreferredEndpoint{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_host_store_v1_host_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PreferredEndpoint) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PreferredEndpoint) ProtoMessage() {} + +func (x *PreferredEndpoint) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_host_store_v1_host_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PreferredEndpoint.ProtoReflect.Descriptor instead. +func (*PreferredEndpoint) Descriptor() ([]byte, []int) { + return file_controller_storage_host_store_v1_host_proto_rawDescGZIP(), []int{3} +} + +func (x *PreferredEndpoint) GetHostSetId() string { + if x != nil { + return x.HostSetId + } + return "" +} + +func (x *PreferredEndpoint) GetPriority() uint32 { + if x != nil { + return x.Priority + } + return 0 +} + +func (x *PreferredEndpoint) GetCondition() string { + if x != nil { + return x.Condition + } + return "" +} + +type IpAddress struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // @inject_tag: `gorm:"primary_key"` + HostId string `protobuf:"bytes,1,opt,name=host_id,json=hostId,proto3" json:"host_id,omitempty" gorm:"primary_key"` + // @inject_tag: `gorm:"primary_key"` + Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty" gorm:"primary_key"` +} + +func (x *IpAddress) Reset() { + *x = IpAddress{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_host_store_v1_host_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *IpAddress) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IpAddress) ProtoMessage() {} + +func (x *IpAddress) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_host_store_v1_host_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use IpAddress.ProtoReflect.Descriptor instead. +func (*IpAddress) Descriptor() ([]byte, []int) { + return file_controller_storage_host_store_v1_host_proto_rawDescGZIP(), []int{4} +} + +func (x *IpAddress) GetHostId() string { + if x != nil { + return x.HostId + } + return "" +} + +func (x *IpAddress) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +type DnsName struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // @inject_tag: `gorm:"primary_key"` + HostId string `protobuf:"bytes,1,opt,name=host_id,json=hostId,proto3" json:"host_id,omitempty" gorm:"primary_key"` + // @inject_tag: `gorm:"primary_key"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty" gorm:"primary_key"` +} + +func (x *DnsName) Reset() { + *x = DnsName{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_host_store_v1_host_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DnsName) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DnsName) ProtoMessage() {} + +func (x *DnsName) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_host_store_v1_host_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DnsName.ProtoReflect.Descriptor instead. +func (*DnsName) Descriptor() ([]byte, []int) { + return file_controller_storage_host_store_v1_host_proto_rawDescGZIP(), []int{5} +} + +func (x *DnsName) GetHostId() string { + if x != nil { + return x.HostId + } + return "" +} + +func (x *DnsName) GetName() string { + if x != nil { + return x.Name + } + return "" +} + var File_controller_storage_host_store_v1_host_proto protoreflect.FileDescriptor var file_controller_storage_host_store_v1_host_proto_rawDesc = []byte{ @@ -219,11 +402,26 @@ var file_controller_storage_host_store_v1_host_proto_rawDesc = []byte{ 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x49, 0x64, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, - 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, - 0x6e, 0x61, 0x6c, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, - 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x49, 0x64, 0x22, 0x6d, 0x0a, 0x11, 0x50, 0x72, 0x65, + 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1e, + 0x0a, 0x0b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x68, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x49, 0x64, 0x12, 0x1a, + 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, + 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, + 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3e, 0x0a, 0x09, 0x49, 0x70, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x68, 0x6f, 0x73, 0x74, 0x49, 0x64, 0x12, 0x18, + 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x36, 0x0a, 0x07, 0x44, 0x6e, 0x73, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x68, 0x6f, 0x73, 0x74, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, + 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, + 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2f, + 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( @@ -238,11 +436,14 @@ func file_controller_storage_host_store_v1_host_proto_rawDescGZIP() []byte { return file_controller_storage_host_store_v1_host_proto_rawDescData } -var file_controller_storage_host_store_v1_host_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_controller_storage_host_store_v1_host_proto_msgTypes = make([]protoimpl.MessageInfo, 6) var file_controller_storage_host_store_v1_host_proto_goTypes = []interface{}{ - (*Catalog)(nil), // 0: controller.storage.host.store.v1.Catalog - (*Host)(nil), // 1: controller.storage.host.store.v1.Host - (*Set)(nil), // 2: controller.storage.host.store.v1.Set + (*Catalog)(nil), // 0: controller.storage.host.store.v1.Catalog + (*Host)(nil), // 1: controller.storage.host.store.v1.Host + (*Set)(nil), // 2: controller.storage.host.store.v1.Set + (*PreferredEndpoint)(nil), // 3: controller.storage.host.store.v1.PreferredEndpoint + (*IpAddress)(nil), // 4: controller.storage.host.store.v1.IpAddress + (*DnsName)(nil), // 5: controller.storage.host.store.v1.DnsName } var file_controller_storage_host_store_v1_host_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type @@ -294,6 +495,42 @@ func file_controller_storage_host_store_v1_host_proto_init() { return nil } } + file_controller_storage_host_store_v1_host_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PreferredEndpoint); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_storage_host_store_v1_host_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*IpAddress); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_storage_host_store_v1_host_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DnsName); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -301,7 +538,7 @@ func file_controller_storage_host_store_v1_host_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_controller_storage_host_store_v1_host_proto_rawDesc, NumEnums: 0, - NumMessages: 3, + NumMessages: 6, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/host/subtype.go b/internal/host/subtype.go index 1508eff93..b9da2e5c6 100644 --- a/internal/host/subtype.go +++ b/internal/host/subtype.go @@ -1,6 +1,8 @@ package host -import "github.com/hashicorp/boundary/internal/types/subtypes" +import ( + "github.com/hashicorp/boundary/internal/types/subtypes" +) var registry = subtypes.NewRegistry() diff --git a/internal/libs/endpoint/doc.go b/internal/libs/endpoint/doc.go new file mode 100644 index 000000000..74bcbd740 --- /dev/null +++ b/internal/libs/endpoint/doc.go @@ -0,0 +1,10 @@ +package endpoint + +// This package contains enpoint-related libraries. +// +// Currently, this consists only of a preference chooser that, given inputs of +// IP addresses/DNS names and a user-defined preference string, can select the +// most preferred endpoint to use. If no user-defined preference string is +// supplied, an endpoint is selected at random. Creating a preferencer will +// validate input, so calling NewPreferencer and ignoring the returned struct is +// a fine way to validate incoming preference order statements. diff --git a/internal/libs/endpoint/matcher.go b/internal/libs/endpoint/matcher.go new file mode 100644 index 000000000..4f8deb79a --- /dev/null +++ b/internal/libs/endpoint/matcher.go @@ -0,0 +1,48 @@ +package endpoint + +import ( + "net" + + "github.com/ryanuber/go-glob" +) + +// matcher is a function that given an input returns whether there is a match +type matcher interface { + Match(string) bool +} + +var ( + _ matcher = (*dnsMatcher)(nil) + _ matcher = (*cidrMatcher)(nil) +) + +// DnsMatcher is a function that given an input returns true if there is a +// globbed DNS match +type dnsMatcher struct { + pattern string +} + +// Match satisfies the matcher interface +func (m dnsMatcher) Match(in string) bool { + return glob.Glob(m.pattern, in) +} + +// cidrMatcher is a function that given an input returns true if the input is +// contained within the given net +type cidrMatcher struct { + ipNet *net.IPNet +} + +// Match satisfies the matcher interface +func (m cidrMatcher) Match(in string) bool { + // Can't be created directly since this is unexported so this should never + // actually trigger, but for safety + if m.ipNet == nil { + return false + } + ip := net.ParseIP(in) + if ip == nil { + return false + } + return m.ipNet.Contains(ip) +} diff --git a/internal/libs/endpoint/matcher_test.go b/internal/libs/endpoint/matcher_test.go new file mode 100644 index 000000000..1298a1a48 --- /dev/null +++ b/internal/libs/endpoint/matcher_test.go @@ -0,0 +1,48 @@ +package endpoint + +import ( + "net" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMatchers(t *testing.T) { + t.Parallel() + t.Run("dnsMatcherEmptyPattern", func(t *testing.T) { + d := dnsMatcher{pattern: ""} + assert.False(t, d.Match("foo")) + }) + t.Run("dnsMatcherBasic", func(t *testing.T) { + d := dnsMatcher{pattern: "foo"} + assert.True(t, d.Match("foo")) + }) + t.Run("dnsMatcherMatchingGlob", func(t *testing.T) { + d := dnsMatcher{pattern: "fo*o*"} + assert.True(t, d.Match("foweroasgdf")) + }) + t.Run("dnsMatcherNonMatchingGlob", func(t *testing.T) { + d := dnsMatcher{pattern: "fo*o*"} + assert.False(t, d.Match("bfoweroasgdf")) + }) + t.Run("cidrMatcherNilMatcher", func(t *testing.T) { + d := cidrMatcher{} + assert.False(t, d.Match("1.2.3.4")) + }) + t.Run("cidrMatcherIpv4Matcher", func(t *testing.T) { + _, net, err := net.ParseCIDR("1.2.3.4/24") + require.NoError(t, err) + d := cidrMatcher{ipNet: net} + assert.True(t, d.Match("1.2.3.4")) + assert.False(t, d.Match("1.2.4.4")) + assert.False(t, d.Match("260.234.19.3")) + }) + t.Run("cidrMatcherIpv6Matcher", func(t *testing.T) { + _, net, err := net.ParseCIDR("2001:1234::/32") + require.NoError(t, err) + d := cidrMatcher{ipNet: net} + assert.True(t, d.Match("2001:1234:3092::abcd:dead:beef:2423")) + assert.False(t, d.Match("2001:1244:3092::abcd:dead:beef:2423")) + }) +} diff --git a/internal/libs/endpoint/option.go b/internal/libs/endpoint/option.go new file mode 100644 index 000000000..bf70a8624 --- /dev/null +++ b/internal/libs/endpoint/option.go @@ -0,0 +1,106 @@ +package endpoint + +import ( + "fmt" + "net" + "strings" +) + +// getOpts iterates the inbound Options and returns a struct +func getOpts(opt ...Option) (options, error) { + opts := getDefaultOptions() + for _, o := range opt { + if o == nil { + continue + } + if err := o(&opts); err != nil { + return options{}, err + } + } + return opts, nil +} + +// Option - how Options are passed as arguments +type Option func(*options) error + +// options = how options are represented +type options struct { + withIpAddrs []string + withDnsNames []string + withMatchers []matcher +} + +func getDefaultOptions() options { + return options{} +} + +// WithIpAddrs contains IP addresses to add into the endpoint possibilities. +// If an IP cannot be parsed, this function will error. +func WithIpAddrs(with []string) Option { + return func(o *options) error { + for _, addr := range with { + ip := net.ParseIP(addr) + if ip == nil { + return fmt.Errorf("input '%s' is not parseable as an ip address", addr) + } + o.withIpAddrs = append(o.withIpAddrs, addr) + } + return nil + } +} + +// WithDnsNames contains DNS names to add into the endpoint possibilities +func WithDnsNames(with []string) Option { + return func(o *options) error { + o.withDnsNames = with + return nil + } +} + +// WithPreferenceOrder contains the preference order specification. If one of +// the preferences cannot be parsed, this function will error. Internally it +// builds up a set of matchers. +func WithPreferenceOrder(with []string) Option { + return func(o *options) error { + for _, input := range with { + var m matcher + switch { + case strings.HasPrefix(input, "cidr:"): + // Make sure ParseCIDR won't choke on a bare address + cidr := strings.TrimPrefix(input, "cidr:") + if !strings.Contains(cidr, "/") { + // See if it seems like an IPv6 address vs. IPv4; colons are + // a good way to check this + if strings.Contains(cidr, ":") { + cidr = fmt.Sprintf("%s/128", cidr) + } else { + cidr = fmt.Sprintf("%s/32", cidr) + } + } + _, ipNet, err := net.ParseCIDR(cidr) + if err != nil { + return fmt.Errorf("error parsing cidr %s: %w", cidr, err) + } + m = cidrMatcher{ + ipNet: ipNet, + } + + case strings.HasPrefix(input, "dns:"): + pattern := strings.TrimPrefix(input, "dns:") + if pattern == "" { + return fmt.Errorf("empty dns pattern provided") + } + m = dnsMatcher{ + pattern: pattern, + } + + default: + return fmt.Errorf("preference string %q is not supported", input) + } + if m != nil { + o.withMatchers = append(o.withMatchers, m) + } + } + return nil + } +} diff --git a/internal/libs/endpoint/options_test.go b/internal/libs/endpoint/options_test.go new file mode 100644 index 000000000..1c87293df --- /dev/null +++ b/internal/libs/endpoint/options_test.go @@ -0,0 +1,79 @@ +package endpoint + +import ( + "net" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_GetOpts(t *testing.T) { + t.Parallel() + t.Run("WithNil", func(t *testing.T) { + _, err := getOpts(Option(nil)) + require.NoError(t, err) + }) + t.Run("WithDnsNames", func(t *testing.T) { + opts, err := getOpts(WithDnsNames([]string{"foo.bar", "fluebar"})) + require.NoError(t, err) + testOpts := getDefaultOptions() + testOpts.withDnsNames = []string{"foo.bar", "fluebar"} + assert.Equal(t, opts, testOpts) + }) + t.Run("WithIpAddrsBadIp", func(t *testing.T) { + _, err := getOpts(WithIpAddrs([]string{"foo.bar", "1.2.3.4"})) + require.Error(t, err) + }) + t.Run("WithIpAddrs", func(t *testing.T) { + opts, err := getOpts(WithIpAddrs([]string{"1.2.3.4", "5.6.7.8"})) + require.NoError(t, err) + testOpts := getDefaultOptions() + testOpts.withIpAddrs = []string{"1.2.3.4", "5.6.7.8"} + assert.Equal(t, opts, testOpts) + }) + t.Run("WithPreferenceOrderBadCidr", func(t *testing.T) { + _, err := getOpts(WithPreferenceOrder([]string{"cidr:15.3.25.6/33", "1.2.3.4"})) + require.Error(t, err) + }) + t.Run("WithPreferenceOrderBadDns", func(t *testing.T) { + _, err := getOpts(WithPreferenceOrder([]string{"dns:"})) + require.Error(t, err) + }) + t.Run("WithPreferenceOrderBadPref", func(t *testing.T) { + _, err := getOpts(WithPreferenceOrder([]string{"abc:15.3.25.6/33", "1.2.3.4"})) + require.Error(t, err) + }) + t.Run("WithPreferenceOrder", func(t *testing.T) { + require := require.New(t) + cidr1Str := "15.3.25.6/8" + _, net1, err := net.ParseCIDR(cidr1Str) + require.NoError(err) + dns1Str := "foo.bar" + cidr2Str := "2001::44" + _, net2, err := net.ParseCIDR(cidr2Str + "/128") + require.NoError(err) + cidr3Str := "1.2.3.4" + _, net3, err := net.ParseCIDR(cidr3Str + "/32") + require.NoError(err) + opts, err := getOpts(WithPreferenceOrder([]string{"cidr:" + cidr1Str, "dns:" + dns1Str, "cidr:" + cidr2Str, "cidr:" + cidr3Str})) + require.NoError(err) + testOpts := getDefaultOptions() + + testOpts.withMatchers = []matcher{ + cidrMatcher{ + ipNet: net1, + }, + dnsMatcher{ + pattern: dns1Str, + }, + cidrMatcher{ + ipNet: net2, + }, + cidrMatcher{ + ipNet: net3, + }, + } + assert.Equal(t, opts, testOpts) + }) +} diff --git a/internal/libs/endpoint/preferencer.go b/internal/libs/endpoint/preferencer.go new file mode 100644 index 000000000..ab838855a --- /dev/null +++ b/internal/libs/endpoint/preferencer.go @@ -0,0 +1,117 @@ +package endpoint + +import ( + "context" + "net" + + "github.com/hashicorp/boundary/internal/errors" +) + +type preferencer struct { + matchers []matcher +} + +// NewPreferencer builds up a preferencer with a set of preference options. This +// can then be used with Choose to select preferences from among a set of IP +// addresses and DNS names. +// +// Supported options: WithPreferenceOrder +func NewPreferencer(ctx context.Context, opt ...Option) (*preferencer, error) { + const op = "endpoint.NewPreferencer" + opts, err := getOpts(opt...) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithCode(errors.InvalidParameter)) + } + pref := &preferencer{ + matchers: opts.withMatchers, + } + return pref, nil +} + +// Choose takes in IP addresses and/or DNS names and chooses an endpoint from +// among them, picking one at random if there are no preferences supplied. If +// preferences are specified but none match, the empty string is returned. +// However, if no IP addresses or DNS names are supplied, an error is returned. +// +// Supported options: WithIpAddrs, WithDnsNames +func (p *preferencer) Choose(ctx context.Context, opt ...Option) (string, error) { + const op = "endpoint.(preferencer).Choose" + opts, err := getOpts(opt...) + if err != nil { + return "", errors.Wrap(ctx, err, op) + } + if len(opts.withIpAddrs)+len(opts.withDnsNames) == 0 { + return "", errors.New(ctx, errors.InvalidParameter, op, "no ip addresses or dns names passed in") + } + + switch len(p.matchers) { + case 0: + // We have no matchers, so pick a private address if we have one (since + // those are most likely to be what the user is trying to gain access to + // via a worker). If we don't have one, pick any IP address, as that's + // also more likely desired than a DNS name. Otherwise, pick a DNS name. + // IPv6 follows same rules as 4, but least preferenced. + nonPrivateIp4s := make([]string, 0, len(opts.withIpAddrs)) + nonPrivateIp6s := make([]string, 0, len(opts.withIpAddrs)) + privateIp6s := make([]string, 0, len(opts.withIpAddrs)) + + for _, ipStr := range opts.withIpAddrs { + ipVal := net.ParseIP(ipStr) + if ipVal != nil { + switch ipVal.To4() { + case nil: // it's v6 + if ipVal.IsPrivate() { + privateIp6s = append(privateIp6s, ipStr) + } else { + nonPrivateIp6s = append(nonPrivateIp6s, ipStr) + } + default: + if ipVal.IsPrivate() { + // private IPv4 is most highly preferenced, so return it directly + return ipStr, nil + } + nonPrivateIp4s = append(nonPrivateIp4s, ipStr) + } + } + } + + switch { + case len(nonPrivateIp4s) > 0: + return nonPrivateIp4s[0], nil + + case len(opts.withDnsNames) > 0: + return opts.withDnsNames[0], nil + + case len(privateIp6s) > 0: + return privateIp6s[0], nil + + case len(nonPrivateIp6s) > 0: + return nonPrivateIp6s[0], nil + + default: + // We literally have nothing if we get here, so return nothing + return "", nil + } + + default: + for _, m := range p.matchers { + switch m.(type) { + case dnsMatcher: + for _, name := range opts.withDnsNames { + if m.Match(name) { + return name, nil + } + } + case cidrMatcher: + for _, addr := range opts.withIpAddrs { + if m.Match(addr) { + return addr, nil + } + } + } + } + // Nothing matched. Don't treat it as an error, let the calling function + // simply ignore the empty result. + return "", nil + } +} diff --git a/internal/libs/endpoint/preferencer_test.go b/internal/libs/endpoint/preferencer_test.go new file mode 100644 index 000000000..0fd904a60 --- /dev/null +++ b/internal/libs/endpoint/preferencer_test.go @@ -0,0 +1,152 @@ +package endpoint + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPreferencer(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + t.Run("badOption", func(t *testing.T) { + _, err := NewPreferencer(ctx, WithPreferenceOrder([]string{"bad:1.2.3.4"})) + assert.Error(t, err) + }) + t.Run("goodOption", func(t *testing.T) { + _, err := NewPreferencer(ctx, WithPreferenceOrder([]string{"cidr:1.2.3.4"})) + assert.NoError(t, err) + }) + t.Run("chooseBadOption", func(t *testing.T) { + p, err := NewPreferencer(ctx) + require.NoError(t, err) + _, err = p.Choose(ctx, WithIpAddrs([]string{"266.1.2.3"})) + assert.Error(t, err) + }) + t.Run("noAddresses", func(t *testing.T) { + p, err := NewPreferencer(ctx) + require.NoError(t, err) + _, err = p.Choose(ctx) + assert.Error(t, err) + }) + t.Run("preferenceOrder", func(t *testing.T) { + p, err := NewPreferencer(ctx, + WithPreferenceOrder([]string{ + "cidr:1.2.3.4/24", + "dns:*.example.com", + "cidr:5.6.7.8/8", + "dns:*.company.com", + })) + require.NoError(t, err) + + cases := []struct { + name string + withIpAddrs []string + withDnsNames []string + expectedEndpoint string + expectedErrorContains string + }{ + { + name: "first cidr", + withIpAddrs: []string{"5.6.7.8", "1.2.3.56"}, + withDnsNames: []string{"foo.bar", "bar.baz"}, + expectedEndpoint: "1.2.3.56", + }, + { + name: "first dns", + withIpAddrs: []string{"48.134.5.1", "1.2.7.56"}, + withDnsNames: []string{"bar.baz", "foo.example.com"}, + expectedEndpoint: "foo.example.com", + }, + { + name: "second cidr", + withIpAddrs: []string{"5.6.7.8", "1.2.7.56"}, + withDnsNames: []string{"foo.bar", "bar.baz"}, + expectedEndpoint: "5.6.7.8", + }, + { + name: "second dns", + withIpAddrs: []string{"48.134.5.1", "1.2.7.56"}, + withDnsNames: []string{"foo.bar.com", "bar.company.com"}, + expectedEndpoint: "bar.company.com", + }, + { + name: "no match", + withIpAddrs: []string{"48.134.5.1", "1.2.7.56"}, + withDnsNames: []string{"foo.bar.com", "bar.baz.com"}, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + out, err := p.Choose(ctx, WithIpAddrs(tc.withIpAddrs), WithDnsNames(tc.withDnsNames)) + if tc.expectedErrorContains != "" { + require.Error(t, err) + assert.Contains(t, tc.expectedErrorContains, err.Error()) + } + assert.Equal(t, tc.expectedEndpoint, out) + }) + } + }) + t.Run("noPrefReturnsPrivate", func(t *testing.T) { + const privAddr = "192.168.4.3" + p, err := NewPreferencer(ctx) + require.NoError(t, err) + out, err := p.Choose( + ctx, + WithIpAddrs([]string{"48.134.5.1", "2001::1", privAddr}), + WithDnsNames([]string{"foo.bar.com", "bar.baz.com"}), + ) + require.NoError(t, err) + assert.Equal(t, privAddr, out) + }) + t.Run("noPrefNoPrivateReturnsIp4", func(t *testing.T) { + const exp = "48.134.5.1" + p, err := NewPreferencer(ctx) + require.NoError(t, err) + out, err := p.Choose( + ctx, + WithIpAddrs([]string{"2001::1", exp}), + WithDnsNames([]string{"foo.bar.com", "bar.baz.com"}), + ) + require.NoError(t, err) + assert.Equal(t, exp, out) + }) + t.Run("noPrefNoIp4ReturnsDns", func(t *testing.T) { + const exp = "foo.bar.com" + p, err := NewPreferencer(ctx) + require.NoError(t, err) + out, err := p.Choose( + ctx, + WithIpAddrs([]string{"2001::1"}), + WithDnsNames([]string{exp}), + ) + require.NoError(t, err) + assert.Equal(t, exp, out) + }) + t.Run("ip6OnlyReturnsPrivate", func(t *testing.T) { + const exp = "fc00::1" + p, err := NewPreferencer(ctx) + require.NoError(t, err) + out, err := p.Choose( + ctx, + WithIpAddrs([]string{"2001::1", exp}), + ) + require.NoError(t, err) + assert.Equal(t, exp, out) + }) + t.Run("ip6OnlyPublic", func(t *testing.T) { + const exp = "2001::1" + p, err := NewPreferencer(ctx) + require.NoError(t, err) + out, err := p.Choose( + ctx, + WithIpAddrs([]string{exp}), + ) + require.NoError(t, err) + assert.Equal(t, exp, out) + }) +} diff --git a/internal/libs/patchstruct/patchstruct.go b/internal/libs/patchstruct/patchstruct.go new file mode 100644 index 000000000..313edb821 --- /dev/null +++ b/internal/libs/patchstruct/patchstruct.go @@ -0,0 +1,91 @@ +package patchstruct + +import ( + "fmt" + + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/structpb" +) + +// PatchStruct updates the struct found in dst with the values found in src. +// The intent of this helper is to provide a fallback mechanism for subtype +// attributes when the actual schema of the subtype attributes are unknown. As +// such, it's preferred to use other methods (such as mask mapping) when an +// actual message for the subtype is known. +// +// The following rules apply: +// +// * The source (src) map is merged into the destination map (dst). +// +// * Values are overwritten by the source map if they exist in both. +// +// * Values are deleted from the destination if they are set to null in the +// source. +// +// * Maps are recursively applied, meaning that a nested map at key "foo" in +// the destination would be patched with a map at key "foo" in the +// source. +// +// * A map in the destination is overwritten by a non-map in the source, +// and a non-map in the destination is overwritten by a map in the +// source. +// +// PatchStruct returns the updated map as a copy, dst and src are not altered. +func PatchStruct(dst, src *structpb.Struct) *structpb.Struct { + result, err := structpb.NewStruct(patchM(dst.AsMap(), src.AsMap())) + if err != nil { + // Should never error as values are source from structpb values + panic(err) + } + + return result +} + +// PatchBytes follows the same rules as above with PatchStruct, but +// instead of patching structpb.Structs, it patches the protobuf +// encoding. An error is returned if there are issues working with +// the data. +func PatchBytes(dst, src []byte) ([]byte, error) { + srcpb, dstpb := new(structpb.Struct), new(structpb.Struct) + if err := proto.Unmarshal(dst, dstpb); err != nil { + return nil, fmt.Errorf("error reading destination data: %w", err) + } + + if err := proto.Unmarshal(src, srcpb); err != nil { + return nil, fmt.Errorf("error reading source data: %w", err) + } + + result, err := proto.Marshal(PatchStruct(dstpb, srcpb)) + if err != nil { + return nil, fmt.Errorf("error writing result data: %w", err) + } + + return result, nil +} + +func patchM(dst, src map[string]interface{}) map[string]interface{} { + for k, v := range src { + switch x := v.(type) { + case map[string]interface{}: + if y, ok := dst[k].(map[string]interface{}); ok { + // If the value in dst a map, continue to patch + dst[k] = patchM(y, x) + } else { + // Overwrite after stripping out keys to nil values + newX := patchM(make(map[string]interface{}), x) + dst[k] = newX + } + + default: + if v == nil { + // explicit null values delete values at that key in dst + delete(dst, k) + } else { + // Anything else gets overwritten + dst[k] = v + } + } + } + + return dst +} diff --git a/internal/libs/patchstruct/patchstruct_test.go b/internal/libs/patchstruct/patchstruct_test.go new file mode 100644 index 000000000..efb8f192f --- /dev/null +++ b/internal/libs/patchstruct/patchstruct_test.go @@ -0,0 +1,237 @@ +package patchstruct + +import ( + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/boundary/internal/host" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/structpb" +) + +type testCase struct { + name string + dst map[string]interface{} + src map[string]interface{} + expected map[string]interface{} +} + +var testCases = []testCase{ + { + name: "merge", + dst: map[string]interface{}{ + "foo": "bar", + }, + src: map[string]interface{}{ + "baz": "qux", + }, + expected: map[string]interface{}{ + "foo": "bar", + "baz": "qux", + }, + }, + { + name: "overwrite", + dst: map[string]interface{}{ + "foo": "bar", + }, + src: map[string]interface{}{ + "foo": "baz", + }, + expected: map[string]interface{}{ + "foo": "baz", + }, + }, + { + name: "delete", + dst: map[string]interface{}{ + "foo": "bar", + "baz": "qux", + }, + src: map[string]interface{}{ + "baz": nil, + }, + expected: map[string]interface{}{ + "foo": "bar", + }, + }, + { + name: "recursive", + dst: map[string]interface{}{ + "nested": map[string]interface{}{ + "a": "b", + }, + "foo": "bar", + }, + src: map[string]interface{}{ + "nested": map[string]interface{}{ + "c": "d", + }, + }, + expected: map[string]interface{}{ + "nested": map[string]interface{}{ + "a": "b", + "c": "d", + }, + "foo": "bar", + }, + }, + { + name: "nested with nil", + dst: nil, + src: map[string]interface{}{ + "a": "b", + "nested": map[string]interface{}{ + "c": "d", + "e": nil, + }, + "f": nil, + }, + expected: map[string]interface{}{ + "a": "b", + "nested": map[string]interface{}{ + "c": "d", + }, + }, + }, + { + name: "overwrite with map in src", + dst: map[string]interface{}{ + "foo": "bar", + }, + src: map[string]interface{}{ + "foo": map[string]interface{}{ + "a": "b", + }, + }, + expected: map[string]interface{}{ + "foo": map[string]interface{}{ + "a": "b", + }, + }, + }, + { + name: "nil src", + dst: map[string]interface{}{ + "foo": "bar", + }, + src: nil, + expected: map[string]interface{}{ + "foo": "bar", + }, + }, + { + name: "nil dst", + dst: nil, + src: map[string]interface{}{ + "foo": "bar", + }, + expected: map[string]interface{}{ + "foo": "bar", + }, + }, + { + name: "nil dst with src nil value", + dst: nil, + src: map[string]interface{}{ + "foo": "bar", + "fiz": nil, + }, + expected: map[string]interface{}{ + "foo": "bar", + }, + }, +} + +func TestPatchStruct(t *testing.T) { + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + require := require.New(t) + dst, src := mustStruct(tc.dst), mustStruct(tc.src) + dstOrig, srcOrig := mustStruct(tc.dst), mustStruct(tc.src) + if tc.dst == nil { + dst = nil + dstOrig = nil + } + if tc.src == nil { + src = nil + srcOrig = nil + } + + actual := PatchStruct(dst, src) + require.Empty(cmp.Diff(mustStruct(tc.expected), actual, + cmpopts.IgnoreUnexported(structpb.Struct{}, structpb.Value{}), + cmpopts.SortSlices(func(x, y interface{}) bool { + return x.(*host.IpAddress).Address < y.(*host.IpAddress).Address + }))) + require.Equal(dstOrig, dst) + require.Equal(srcOrig, src) + }) + } +} + +func TestPatchBytes(t *testing.T) { + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + require := require.New(t) + dst, src := mustMarshal(tc.dst), mustMarshal(tc.src) + + actual, err := PatchBytes(dst, src) + require.NoError(err) + requireEqualEncoded(t, mustMarshal(tc.expected), actual) + }) + } +} + +func TestPatchBytesErr(t *testing.T) { + t.Run("dst", func(t *testing.T) { + require := require.New(t) + _, err := PatchBytes([]byte("foo"), nil) + require.ErrorIs(err, proto.Error) + require.True(strings.HasPrefix(err.Error(), "error reading destination data: ")) + }) + t.Run("src", func(t *testing.T) { + require := require.New(t) + _, err := PatchBytes(nil, []byte("foo")) + require.ErrorIs(err, proto.Error) + require.True(strings.HasPrefix(err.Error(), "error reading source data: ")) + }) +} + +func mustStruct(in map[string]interface{}) *structpb.Struct { + out, err := structpb.NewStruct(in) + if err != nil { + panic(err) + } + + return out +} + +func mustMarshal(in map[string]interface{}) []byte { + b, err := proto.Marshal(mustStruct(in)) + if err != nil { + panic(err) + } + + return b +} + +func requireEqualEncoded(t *testing.T, expected, actual []byte) { + t.Helper() + require := require.New(t) + + expectedpb, actualpb := new(structpb.Struct), new(structpb.Struct) + + err := proto.Unmarshal(expected, expectedpb) + require.NoError(err) + + err = proto.Unmarshal(actual, actualpb) + require.NoError(err) + + require.Equal(expectedpb, actualpb) +} diff --git a/internal/perms/output_fields.go b/internal/perms/output_fields.go index 9f6a497e1..b50a39d88 100644 --- a/internal/perms/output_fields.go +++ b/internal/perms/output_fields.go @@ -71,6 +71,8 @@ func (o OutputFieldsMap) SelfOrDefaults(userId string) OutputFieldsMap { globals.IdField: true, globals.ScopeField: true, globals.ScopeIdField: true, + globals.PluginIdField: true, + globals.PluginField: true, globals.NameField: true, globals.DescriptionField: true, globals.TypeField: true, diff --git a/internal/perms/output_fields_test.go b/internal/perms/output_fields_test.go index a2f2b96e0..7a7080559 100644 --- a/internal/perms/output_fields_test.go +++ b/internal/perms/output_fields_test.go @@ -253,6 +253,8 @@ func Test_ACLSelfOrDefault(t *testing.T) { globals.IdField: true, globals.ScopeField: true, globals.ScopeIdField: true, + globals.PluginField: true, + globals.PluginIdField: true, globals.NameField: true, globals.DescriptionField: true, globals.TypeField: true, diff --git a/internal/plugin/doc.go b/internal/plugin/doc.go new file mode 100644 index 000000000..85e195c9f --- /dev/null +++ b/internal/plugin/doc.go @@ -0,0 +1,3 @@ +// Package plugin contains other packages related to the different plugin +// types and any helpers related to working with non type specific plugins. +package plugin diff --git a/internal/plugin/host/doc.go b/internal/plugin/host/doc.go new file mode 100644 index 000000000..3ca79e39c --- /dev/null +++ b/internal/plugin/host/doc.go @@ -0,0 +1,4 @@ +// Package host provides a plugin type used to interface with boundary's host +// related resources. Additionally it provides a repository for performing +// CRUDL and custom operations on this plugin type. +package host diff --git a/internal/plugin/host/ids.go b/internal/plugin/host/ids.go new file mode 100644 index 000000000..5aafccb31 --- /dev/null +++ b/internal/plugin/host/ids.go @@ -0,0 +1,19 @@ +package host + +import ( + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" +) + +// PublicId prefixes for the resources in the plugin package. +const ( + PluginPrefix = "pl" +) + +func newPluginId() (string, error) { + id, err := db.NewPublicId(PluginPrefix) + if err != nil { + return "", errors.WrapDeprecated(err, "plugin.newPluginId") + } + return id, nil +} diff --git a/internal/plugin/host/immutable_fields_test.go b/internal/plugin/host/immutable_fields_test.go new file mode 100644 index 000000000..ebb2faf3b --- /dev/null +++ b/internal/plugin/host/immutable_fields_test.go @@ -0,0 +1,76 @@ +package host + +import ( + "context" + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/db/timestamp" + "github.com/hashicorp/boundary/internal/plugin/host/store" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func TestHostPlugin_ImmutableFields(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + w := db.New(conn) + + ts := timestamp.Timestamp{Timestamp: ×tamppb.Timestamp{Seconds: 0, Nanos: 0}} + plg := TestPlugin(t, conn, "test") + + newPlugin := plg + + tests := []struct { + name string + update *Plugin + fieldMask []string + }{ + { + name: "public_id", + update: func() *Plugin { + c := newPlugin.testClonePlugin() + c.PublicId = "hc_thisIsNotAValidId" + return c + }(), + fieldMask: []string{"PublicId"}, + }, + { + name: "create time", + update: func() *Plugin { + c := newPlugin.testClonePlugin() + c.CreateTime = &ts + return c + }(), + fieldMask: []string{"CreateTime"}, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + orig := newPlugin.testClonePlugin() + err := w.LookupById(context.Background(), orig) + require.NoError(err) + + rowsUpdated, err := w.Update(context.Background(), tt.update, tt.fieldMask, nil, db.WithSkipVetForWrite(true)) + require.Error(err) + assert.Equal(0, rowsUpdated) + + after := newPlugin.testClonePlugin() + err = w.LookupById(context.Background(), after) + require.NoError(err) + + assert.True(proto.Equal(orig, after)) + }) + } +} + +func (c *Plugin) testClonePlugin() *Plugin { + cp := proto.Clone(c.Plugin) + return &Plugin{ + Plugin: cp.(*store.Plugin), + } +} diff --git a/internal/plugin/host/options.go b/internal/plugin/host/options.go new file mode 100644 index 000000000..4bfbe5d13 --- /dev/null +++ b/internal/plugin/host/options.go @@ -0,0 +1,58 @@ +package host + +// GetOpts - iterate the inbound Options and return a struct +func GetOpts(opt ...Option) options { + opts := getDefaultOptions() + for _, o := range opt { + o(&opts) + } + return opts +} + +// Option - how Options are passed as arguments. +type Option func(*options) + +// options = how options are represented +type options struct { + withName string + withDescription string + withPublicId string + withLimit int +} + +func getDefaultOptions() options { + return options{ + withDescription: "", + withName: "", + } +} + +// WithDescription provides an optional description. +func WithDescription(desc string) Option { + return func(o *options) { + o.withDescription = desc + } +} + +// WithName provides an optional name. +func WithName(name string) Option { + return func(o *options) { + o.withName = name + } +} + +// WithPublicId provides an optional specific public ID +func WithPublicId(with string) Option { + return func(o *options) { + o.withPublicId = with + } +} + +// WithLimit provides an option to provide a limit. Intentionally allowing +// negative integers. If WithLimit < 0, then unlimited results are +// returned. If WithLimit == 0, then default limits are used for results. +func WithLimit(l int) Option { + return func(o *options) { + o.withLimit = l + } +} diff --git a/internal/plugin/host/options_test.go b/internal/plugin/host/options_test.go new file mode 100644 index 000000000..7abe5829d --- /dev/null +++ b/internal/plugin/host/options_test.go @@ -0,0 +1,35 @@ +package host + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_GetOpts(t *testing.T) { + t.Parallel() + t.Run("WithName", func(t *testing.T) { + opts := GetOpts(WithName("test")) + testOpts := getDefaultOptions() + testOpts.withName = "test" + assert.Equal(t, opts, testOpts) + }) + t.Run("WithDescription", func(t *testing.T) { + opts := GetOpts(WithDescription("test desc")) + testOpts := getDefaultOptions() + testOpts.withDescription = "test desc" + assert.Equal(t, opts, testOpts) + }) + t.Run("WithPublicId", func(t *testing.T) { + opts := GetOpts(WithPublicId("pl_1234567890")) + testOpts := getDefaultOptions() + testOpts.withPublicId = "pl_1234567890" + assert.Equal(t, opts, testOpts) + }) + t.Run("WithLimit", func(t *testing.T) { + opts := GetOpts(WithLimit(5)) + testOpts := getDefaultOptions() + testOpts.withLimit = 5 + assert.Equal(t, opts, testOpts) + }) +} diff --git a/internal/plugin/host/plugin.go b/internal/plugin/host/plugin.go new file mode 100644 index 000000000..fc5ec1c4f --- /dev/null +++ b/internal/plugin/host/plugin.go @@ -0,0 +1,68 @@ +package host + +import ( + "github.com/hashicorp/boundary/internal/oplog" + "github.com/hashicorp/boundary/internal/plugin/host/store" + "github.com/hashicorp/boundary/internal/types/scope" + "google.golang.org/protobuf/proto" +) + +// A Plugin enables additional logic to be used by boundary. +// It is owned by a scope. +type Plugin struct { + *store.Plugin + tableName string `gorm:"-"` +} + +// NewPlugin creates a new in memory Plugin assigned to the global scope. +// Name, Description are the only allowed option. All other options are ignored. +func NewPlugin(opt ...Option) *Plugin { + opts := GetOpts(opt...) + p := &Plugin{ + Plugin: &store.Plugin{ + ScopeId: scope.Global.String(), + Name: opts.withName, + Description: opts.withDescription, + }, + } + return p +} + +// TableName returns the table name for the host plugin. +func (c *Plugin) TableName() string { + if c.tableName != "" { + return c.tableName + } + return "plugin_host" +} + +// SetTableName sets the table name. If the caller attempts to +// set the name to "" the name will be reset to the default name. +func (c *Plugin) SetTableName(n string) { + c.tableName = n +} + +func allocPlugin() *Plugin { + return &Plugin{ + Plugin: &store.Plugin{}, + } +} + +func (c *Plugin) clone() *Plugin { + cp := proto.Clone(c.Plugin) + return &Plugin{ + Plugin: cp.(*store.Plugin), + } +} + +func newPluginMetadata(p *Plugin, op oplog.OpType) oplog.Metadata { + metadata := oplog.Metadata{ + "resource-public-id": []string{p.GetPublicId()}, + "resource-type": []string{"host plugin"}, + "op-type": []string{op.String()}, + } + if p.ScopeId != "" { + metadata["scope-id"] = []string{p.ScopeId} + } + return metadata +} diff --git a/internal/plugin/host/plugin_test.go b/internal/plugin/host/plugin_test.go new file mode 100644 index 000000000..1d7af800d --- /dev/null +++ b/internal/plugin/host/plugin_test.go @@ -0,0 +1,154 @@ +package host + +import ( + "context" + "testing" + + "github.com/hashicorp/boundary/internal/types/scope" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/plugin/host/store" +) + +func TestPlugin_Create(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + + tests := []struct { + name string + opts []Option + want *Plugin + wantErr bool + }{ + { + name: "valid", + want: &Plugin{ + Plugin: &store.Plugin{ + ScopeId: scope.Global.String(), + }, + }, + }, + { + name: "with-name", + opts: []Option{WithName("foo")}, + want: &Plugin{ + Plugin: &store.Plugin{ + Name: "foo", + ScopeId: scope.Global.String(), + }, + }, + }, + { + name: "with-description", + opts: []Option{WithDescription("foo")}, + want: &Plugin{ + Plugin: &store.Plugin{ + Description: "foo", + ScopeId: scope.Global.String(), + }, + }, + }, + // This must be run after the "valid-no-options" test + { + name: "duplicate-name", + opts: []Option{WithName("foo")}, + want: &Plugin{ + Plugin: &store.Plugin{ + Name: "foo", + ScopeId: scope.Global.String(), + }, + }, + wantErr: true, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + got := NewPlugin(tt.opts...) + require.NotNil(t, got) + require.Emptyf(t, got.PublicId, "PublicId set") + + id, err := newPluginId() + require.NoError(t, err) + got.PublicId = id + + tt.want.PublicId = id + assert.Equal(t, tt.want, got) + + w := db.New(conn) + err = w.Create(context.Background(), got) + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestPlugin_Update(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + w := db.New(conn) + ctx := context.Background() + plg := TestPlugin(t, conn, "delete-plugin") + assert.Equal(t, uint32(1), plg.Version) + + plg.Name = "New" + plg.Description = "Description" + rowCount, err := w.Update(ctx, plg, []string{"Name", "Description"}, nil) + require.NoError(t, err) + assert.Equal(t, 1, rowCount) + + assert.Equal(t, uint32(2), plg.Version) + assert.Equal(t, "New", plg.Name) + assert.Equal(t, "Description", plg.Description) +} + +func TestPlugin_Delete(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + w := db.New(conn) + ctx := context.Background() + plg := TestPlugin(t, conn, "delete-plugin") + + deleted, err := w.Delete(ctx, plg) + require.NoError(t, err) + require.Equal(t, 1, deleted) +} + +func TestPlugin_SetTableName(t *testing.T) { + defaultTableName := "plugin_host" + tests := []struct { + name string + initialName string + setNameTo string + want string + }{ + { + name: "new-pluginName", + initialName: "", + setNameTo: "new-pluginName", + want: "new-pluginName", + }, + { + name: "reset to default", + initialName: "initial", + setNameTo: "", + want: defaultTableName, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + def := NewPlugin() + require.Equal(defaultTableName, def.TableName()) + s := &Plugin{ + Plugin: &store.Plugin{}, + tableName: tt.initialName, + } + s.SetTableName(tt.setNameTo) + assert.Equal(tt.want, s.TableName()) + }) + } +} diff --git a/internal/plugin/host/repository.go b/internal/plugin/host/repository.go new file mode 100644 index 000000000..f07c0bd72 --- /dev/null +++ b/internal/plugin/host/repository.go @@ -0,0 +1,47 @@ +package host + +import ( + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/kms" +) + +// A Repository stores and retrieves the persistent types in the host +// package. It is not safe to use a repository concurrently. +type Repository struct { + reader db.Reader + writer db.Writer + kms *kms.Kms + // defaultLimit provides a default for limiting the number of results + // returned from the repo + defaultLimit int +} + +// NewRepository creates a new Repository. The returned repository should +// only be used for one transaction and it is not safe for concurrent go +// routines to access it. WithLimit option is used as a repo wide default +// limit applied to all ListX methods. +func NewRepository(r db.Reader, w db.Writer, kms *kms.Kms, opt ...Option) (*Repository, error) { + const op = "static.NewRepository" + switch { + case r == nil: + return nil, errors.NewDeprecated(errors.InvalidParameter, op, "db.Reader") + case w == nil: + return nil, errors.NewDeprecated(errors.InvalidParameter, op, "db.Writer") + case kms == nil: + return nil, errors.NewDeprecated(errors.InvalidParameter, op, "kms") + } + + opts := GetOpts(opt...) + if opts.withLimit == 0 { + // zero signals the boundary defaults should be used. + opts.withLimit = db.DefaultLimit + } + + return &Repository{ + reader: r, + writer: w, + kms: kms, + defaultLimit: opts.withLimit, + }, nil +} diff --git a/internal/plugin/host/repository_plugin.go b/internal/plugin/host/repository_plugin.go new file mode 100644 index 000000000..414840465 --- /dev/null +++ b/internal/plugin/host/repository_plugin.go @@ -0,0 +1,142 @@ +package host + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/oplog" + "github.com/hashicorp/boundary/internal/types/scope" +) + +// CreatePlugin inserts p into the repository and returns a new +// Plugin containing the plugin's PublicId. p is not changed. p must +// contain a valid ScopeID. p must not contain a PublicId. The PublicId is +// generated and assigned by this method. opt is ignored. +// +// Both p.Name and p.Description are optional. If p.Name is set, it must be +// unique within p.ScopeID. +// +// Both p.CreateTime and c.UpdateTime are ignored. +func (r *Repository) CreatePlugin(ctx context.Context, p *Plugin, opt ...Option) (*Plugin, error) { + const op = "host.(Repository).CreatePlugin" + if p == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "nil Plugin") + } + if p.Plugin == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "nil embedded Plugin") + } + if p.ScopeId == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "no scope id") + } + if p.ScopeId != scope.Global.String() { + return nil, errors.New(ctx, errors.InvalidParameter, op, "scope id is not 'global'") + } + if p.PublicId != "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "public id not empty") + } + p = p.clone() + + opts := GetOpts(opt...) + + p.PublicId = opts.withPublicId + if p.PublicId == "" { + var err error + p.PublicId, err = newPluginId() + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + } + + oplogWrapper, err := r.kms.GetWrapper(ctx, p.ScopeId, kms.KeyPurposeOplog) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) + } + + metadata := newPluginMetadata(p, oplog.OpType_OP_TYPE_CREATE) + + var newPlugin *Plugin + _, err = r.writer.DoTx( + ctx, + db.StdRetryCnt, + db.ExpBackoff{}, + func(_ db.Reader, w db.Writer) error { + newPlugin = p.clone() + err := w.Create( + ctx, + newPlugin, + db.WithOplog(oplogWrapper, metadata), + ) + if err != nil { + return errors.Wrap(ctx, err, op) + } + return nil + }, + ) + + if err != nil { + if errors.IsUniqueError(err) { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in scope: %s: name %s already exists", p.ScopeId, p.Name))) + } + return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in scope: %s", p.ScopeId))) + } + return newPlugin, nil +} + +// LookupPlugin returns the Plugin for id. Returns nil, nil if no +// Plugin is found for id. +func (r *Repository) LookupPlugin(ctx context.Context, id string, _ ...Option) (*Plugin, error) { + const op = "host.(Repository).LookupPlugin" + if id == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "no public id") + } + c := allocPlugin() + c.PublicId = id + if err := r.reader.LookupByPublicId(ctx, c); err != nil { + if errors.IsNotFoundError(err) { + return nil, nil + } + return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for: %s", id))) + } + return c, nil +} + +// LookupPluginByName returns the Plugin for a given name. Returns nil, nil if no +// Plugin is found with that plugin name. +func (r *Repository) LookupPluginByName(ctx context.Context, name string, _ ...Option) (*Plugin, error) { + const op = "host.(Repository).LookupPluginByName" + if name == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "no plugin name") + } + p := allocPlugin() + + if err := r.reader.LookupWhere(ctx, p, "name=?", name); err != nil { + if errors.IsNotFoundError(err) { + return nil, nil + } + return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for: %s", name))) + } + return p, nil +} + +// ListPlugins returns a slice of Plugins for the scope IDs. WithLimit is the only option supported. +func (r *Repository) ListPlugins(ctx context.Context, scopeIds []string, opt ...Option) ([]*Plugin, error) { + const op = "host.(Repository).ListPlugins" + if len(scopeIds) == 0 { + return nil, errors.New(ctx, errors.InvalidParameter, op, "no scope id") + } + opts := GetOpts(opt...) + limit := r.defaultLimit + if opts.withLimit != 0 { + // non-zero signals an override of the default limit for the repo. + limit = opts.withLimit + } + var plugins []*Plugin + err := r.reader.SearchWhere(ctx, &plugins, "scope_id in (?)", []interface{}{scopeIds}, db.WithLimit(limit)) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + return plugins, nil +} diff --git a/internal/plugin/host/repository_plugin_test.go b/internal/plugin/host/repository_plugin_test.go new file mode 100644 index 000000000..2ec31fa46 --- /dev/null +++ b/internal/plugin/host/repository_plugin_test.go @@ -0,0 +1,258 @@ +package host + +import ( + "context" + "strings" + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/plugin/host/store" + "github.com/hashicorp/boundary/internal/types/scope" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRepository_CreatePlugin(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + iam.TestRepo(t, conn, wrapper) + kmsCache := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kmsCache) + assert.NoError(t, err) + assert.NotNil(t, repo) + + tests := []struct { + name string + in *Plugin + opts []Option + want *Plugin + wantIsErr errors.Code + }{ + { + name: "nil-plugin", + wantIsErr: errors.InvalidParameter, + }, + { + name: "nil-embedded-plugin", + in: &Plugin{}, + wantIsErr: errors.InvalidParameter, + }, + { + name: "valid-with-name", + in: &Plugin{ + Plugin: &store.Plugin{ + Name: "test-name-repo", + ScopeId: scope.Global.String(), + }, + }, + want: &Plugin{ + Plugin: &store.Plugin{ + Name: "test-name-repo", + ScopeId: scope.Global.String(), + }, + }, + }, + { + name: "valid-with-description", + in: &Plugin{ + Plugin: &store.Plugin{ + Description: "test-description-repo", + ScopeId: scope.Global.String(), + }, + }, + want: &Plugin{ + Plugin: &store.Plugin{ + Description: "test-description-repo", + ScopeId: scope.Global.String(), + }, + }, + }, + { + name: "non-global-scope", + in: &Plugin{ + Plugin: &store.Plugin{ + ScopeId: "o_1234567890", + }, + }, + wantIsErr: errors.InvalidParameter, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + got, err := repo.CreatePlugin(context.Background(), tt.in, tt.opts...) + if tt.wantIsErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "want err: %q got: %q", tt.wantIsErr, err) + assert.Nil(got) + return + } + require.NoError(t, err) + assert.NoError(err) + assert.Empty(tt.in.PublicId) + assert.NotNil(got) + assertPublicId(t, PluginPrefix, got.PublicId) + assert.NotSame(tt.in, got) + assert.Equal(tt.want.Name, got.Name) + assert.Equal(tt.want.Description, got.Description) + assert.Equal(got.CreateTime, got.UpdateTime) + }) + } + + t.Run("invalid-duplicate-names", func(t *testing.T) { + assert := assert.New(t) + kms := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms) + assert.NoError(err) + assert.NotNil(repo) + in := &Plugin{ + Plugin: &store.Plugin{ + ScopeId: scope.Global.String(), + Name: "invalid-duplicate-names", + }, + } + + got, err := repo.CreatePlugin(context.Background(), in) + assert.NoError(err) + assert.NotNil(got) + assertPublicId(t, PluginPrefix, got.PublicId) + assert.NotSame(in, got) + assert.Equal(in.Name, got.Name) + assert.Equal(in.Description, got.Description) + assert.Equal(got.CreateTime, got.UpdateTime) + + got2, err := repo.CreatePlugin(context.Background(), in) + assert.Truef(errors.Match(errors.T(errors.NotUnique), err), "want err code: %v got err: %v", errors.NotUnique, err) + assert.Nil(got2) + }) +} + +func assertPublicId(t *testing.T, prefix, actual string) { + t.Helper() + assert.NotEmpty(t, actual) + parts := strings.Split(actual, "_") + assert.Equalf(t, 2, len(parts), "want one '_' in PublicId, got multiple in %q", actual) + assert.Equalf(t, prefix, parts[0], "PublicId want prefix: %q, got: %q in %q", prefix, parts[0], actual) +} + +func TestRepository_LookupPlugin(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + plg := TestPlugin(t, conn, "test") + badId, err := newPluginId() + assert.NoError(t, err) + assert.NotNil(t, badId) + + tests := []struct { + name string + id string + want *Plugin + wantErr errors.Code + }{ + { + name: "found", + id: plg.GetPublicId(), + want: plg, + }, + { + name: "not-found", + id: badId, + want: nil, + }, + { + name: "bad-public-id", + id: "", + want: nil, + wantErr: errors.InvalidParameter, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + kms := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms) + assert.NoError(err) + assert.NotNil(repo) + + got, err := repo.LookupPlugin(context.Background(), tt.id) + if tt.wantErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err) + return + } + assert.NoError(err) + + switch { + case tt.want == nil: + assert.Nil(got) + case tt.want != nil: + assert.NotNil(got) + assert.Equal(got, tt.want) + } + }) + } +} + +func TestRepository_LookupPluginByName(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + plg := TestPlugin(t, conn, "name123") + + tests := []struct { + name string + pluginName string + want *Plugin + wantErr errors.Code + }{ + { + name: "found", + pluginName: plg.GetName(), + want: plg, + }, + { + name: "not-found", + pluginName: "randomname", + want: nil, + }, + { + name: "emptyname", + pluginName: "", + want: nil, + wantErr: errors.InvalidParameter, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + kms := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms) + assert.NoError(err) + assert.NotNil(repo) + + got, err := repo.LookupPluginByName(context.Background(), tt.pluginName) + if tt.wantErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err) + return + } + assert.NoError(err) + + switch { + case tt.want == nil: + assert.Nil(got) + case tt.want != nil: + assert.NotNil(got) + assert.Equal(got, tt.want) + } + }) + } +} diff --git a/internal/plugin/host/repository_test.go b/internal/plugin/host/repository_test.go new file mode 100644 index 000000000..ba4b57797 --- /dev/null +++ b/internal/plugin/host/repository_test.go @@ -0,0 +1,118 @@ +package host + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/kms" +) + +func TestRepository_New(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + + type args struct { + r db.Reader + w db.Writer + kms *kms.Kms + opts []Option + } + + tests := []struct { + name string + args args + want *Repository + wantIsErr errors.Code + }{ + { + name: "valid", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + }, + want: &Repository{ + reader: rw, + writer: rw, + kms: kmsCache, + defaultLimit: db.DefaultLimit, + }, + }, + { + name: "valid-with-limit", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + opts: []Option{WithLimit(5)}, + }, + want: &Repository{ + reader: rw, + writer: rw, + kms: kmsCache, + defaultLimit: 5, + }, + }, + { + name: "nil-reader", + args: args{ + r: nil, + w: rw, + kms: kmsCache, + }, + want: nil, + wantIsErr: errors.InvalidParameter, + }, + { + name: "nil-writer", + args: args{ + r: rw, + w: nil, + kms: kmsCache, + }, + want: nil, + wantIsErr: errors.InvalidParameter, + }, + { + name: "nil-kms", + args: args{ + r: rw, + w: rw, + kms: nil, + }, + want: nil, + wantIsErr: errors.InvalidParameter, + }, + { + name: "all-nils", + args: args{ + r: nil, + w: nil, + kms: nil, + }, + want: nil, + wantIsErr: errors.InvalidParameter, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + got, err := NewRepository(tt.args.r, tt.args.w, tt.args.kms, tt.args.opts...) + if tt.wantIsErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "want err: %q got: %q", tt.wantIsErr, err) + assert.Nil(got) + return + } + assert.NoError(err) + require.NotNil(got) + assert.Equal(tt.want, got) + }) + } +} diff --git a/internal/plugin/host/store/plugin.pb.go b/internal/plugin/host/store/plugin.pb.go new file mode 100644 index 000000000..413893e11 --- /dev/null +++ b/internal/plugin/host/store/plugin.pb.go @@ -0,0 +1,243 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.17.3 +// source: controller/storage/plugin/host/store/v1/plugin.proto + +// Package store provides protobufs for storing types in the host package. + +package store + +import ( + timestamp "github.com/hashicorp/boundary/internal/db/timestamp" + _ "github.com/hashicorp/boundary/sdk/pbs/controller/protooptions" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Plugin struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // public_id is a surrogate key suitable for use in a public API. + // @inject_tag: `gorm:"primary_key"` + PublicId string `protobuf:"bytes,10,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"` + // The create_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + CreateTime *timestamp.Timestamp `protobuf:"bytes,20,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"` + // The update_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + UpdateTime *timestamp.Timestamp `protobuf:"bytes,30,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" gorm:"default:current_timestamp"` + // name is optional. If set, it must be unique within scope_id. + // @inject_tag: `gorm:"default:null"` + Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty" gorm:"default:null"` + // description is optional. + // @inject_tag: `gorm:"default:null"` + Description string `protobuf:"bytes,50,opt,name=description,proto3" json:"description,omitempty" gorm:"default:null"` + // The scope_id of the owning scope and must be set. + // @inject_tag: `gorm:"not_null"` + ScopeId string `protobuf:"bytes,60,opt,name=scope_id,json=scopeId,proto3" json:"scope_id,omitempty" gorm:"not_null"` + // @inject_tag: `gorm:"default:null"` + Version uint32 `protobuf:"varint,70,opt,name=version,proto3" json:"version,omitempty" gorm:"default:null"` +} + +func (x *Plugin) Reset() { + *x = Plugin{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_plugin_host_store_v1_plugin_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Plugin) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Plugin) ProtoMessage() {} + +func (x *Plugin) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_plugin_host_store_v1_plugin_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Plugin.ProtoReflect.Descriptor instead. +func (*Plugin) Descriptor() ([]byte, []int) { + return file_controller_storage_plugin_host_store_v1_plugin_proto_rawDescGZIP(), []int{0} +} + +func (x *Plugin) GetPublicId() string { + if x != nil { + return x.PublicId + } + return "" +} + +func (x *Plugin) GetCreateTime() *timestamp.Timestamp { + if x != nil { + return x.CreateTime + } + return nil +} + +func (x *Plugin) GetUpdateTime() *timestamp.Timestamp { + if x != nil { + return x.UpdateTime + } + return nil +} + +func (x *Plugin) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Plugin) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *Plugin) GetScopeId() string { + if x != nil { + return x.ScopeId + } + return "" +} + +func (x *Plugin) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +var File_controller_storage_plugin_host_store_v1_plugin_proto protoreflect.FileDescriptor + +var file_controller_storage_plugin_host_store_v1_plugin_proto_rawDesc = []byte{ + 0x0a, 0x34, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f, + 0x72, 0x61, 0x67, 0x65, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x68, 0x6f, 0x73, 0x74, + 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x27, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, + 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, + 0x6e, 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x1a, + 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f, 0x72, + 0x61, 0x67, 0x65, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2f, 0x76, 0x31, + 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x1a, 0x2a, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x63, 0x75, 0x73, + 0x74, 0x6f, 0x6d, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x6f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xdc, 0x02, 0x0a, + 0x06, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, + 0x69, 0x63, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, + 0x69, 0x6d, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, + 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x24, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x10, 0xc2, 0xdd, + 0x29, 0x0c, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x40, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x32, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1e, 0xc2, 0xdd, 0x29, 0x1a, 0x0a, + 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, + 0x69, 0x64, 0x18, 0x3c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x49, + 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x46, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x40, 0x5a, 0x3e, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, + 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x68, 0x6f, 0x73, + 0x74, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_controller_storage_plugin_host_store_v1_plugin_proto_rawDescOnce sync.Once + file_controller_storage_plugin_host_store_v1_plugin_proto_rawDescData = file_controller_storage_plugin_host_store_v1_plugin_proto_rawDesc +) + +func file_controller_storage_plugin_host_store_v1_plugin_proto_rawDescGZIP() []byte { + file_controller_storage_plugin_host_store_v1_plugin_proto_rawDescOnce.Do(func() { + file_controller_storage_plugin_host_store_v1_plugin_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_storage_plugin_host_store_v1_plugin_proto_rawDescData) + }) + return file_controller_storage_plugin_host_store_v1_plugin_proto_rawDescData +} + +var file_controller_storage_plugin_host_store_v1_plugin_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_controller_storage_plugin_host_store_v1_plugin_proto_goTypes = []interface{}{ + (*Plugin)(nil), // 0: controller.storage.plugin.host.store.v1.Plugin + (*timestamp.Timestamp)(nil), // 1: controller.storage.timestamp.v1.Timestamp +} +var file_controller_storage_plugin_host_store_v1_plugin_proto_depIdxs = []int32{ + 1, // 0: controller.storage.plugin.host.store.v1.Plugin.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 1, // 1: controller.storage.plugin.host.store.v1.Plugin.update_time:type_name -> controller.storage.timestamp.v1.Timestamp + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_controller_storage_plugin_host_store_v1_plugin_proto_init() } +func file_controller_storage_plugin_host_store_v1_plugin_proto_init() { + if File_controller_storage_plugin_host_store_v1_plugin_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_controller_storage_plugin_host_store_v1_plugin_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Plugin); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_controller_storage_plugin_host_store_v1_plugin_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_controller_storage_plugin_host_store_v1_plugin_proto_goTypes, + DependencyIndexes: file_controller_storage_plugin_host_store_v1_plugin_proto_depIdxs, + MessageInfos: file_controller_storage_plugin_host_store_v1_plugin_proto_msgTypes, + }.Build() + File_controller_storage_plugin_host_store_v1_plugin_proto = out.File + file_controller_storage_plugin_host_store_v1_plugin_proto_rawDesc = nil + file_controller_storage_plugin_host_store_v1_plugin_proto_goTypes = nil + file_controller_storage_plugin_host_store_v1_plugin_proto_depIdxs = nil +} diff --git a/internal/plugin/host/testing.go b/internal/plugin/host/testing.go new file mode 100644 index 000000000..e9a0e2b9e --- /dev/null +++ b/internal/plugin/host/testing.go @@ -0,0 +1,21 @@ +package host + +import ( + "context" + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/stretchr/testify/require" +) + +func TestPlugin(t *testing.T, conn *db.DB, name string) *Plugin { + t.Helper() + p := NewPlugin(WithName(name)) + id, err := newPluginId() + require.NoError(t, err) + p.PublicId = id + + w := db.New(conn) + require.NoError(t, w.Create(context.Background(), p)) + return p +} diff --git a/internal/plugin/host/testing_test.go b/internal/plugin/host/testing_test.go new file mode 100644 index 000000000..f72a7f755 --- /dev/null +++ b/internal/plugin/host/testing_test.go @@ -0,0 +1,18 @@ +package host + +import ( + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_TestPlugins(t *testing.T) { + assert, require := assert.New(t), require.New(t) + conn, _ := db.TestSetup(t, "postgres") + + plg := TestPlugin(t, conn, "test") + require.NotNil(plg) + assert.NotEmpty(plg.GetPublicId()) +} diff --git a/internal/plugin/immutable_fields_test.go b/internal/plugin/immutable_fields_test.go new file mode 100644 index 000000000..31a2299fe --- /dev/null +++ b/internal/plugin/immutable_fields_test.go @@ -0,0 +1,72 @@ +package plugin + +import ( + "context" + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/plugin/store" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" +) + +func TestPlugin_ImmutableFields(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + w := db.New(conn) + plg := testPlugin(t, conn, "test") + + newPlugin := plg + + tests := []struct { + name string + update *plugin + fieldMask []string + }{ + { + name: "public_id", + update: func() *plugin { + c := newPlugin.testClonePlugin() + c.PublicId = "pi_thisIsNotAValidId" + return c + }(), + fieldMask: []string{"PublicId"}, + }, + { + name: "scope", + update: func() *plugin { + c := newPlugin.testClonePlugin() + c.ScopeId = "o_1234567890" + return c + }(), + fieldMask: []string{"ScopeId"}, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + orig := newPlugin.testClonePlugin() + err := w.LookupById(context.Background(), orig) + require.NoError(err) + + rowsUpdated, err := w.Update(context.Background(), tt.update, tt.fieldMask, nil, db.WithSkipVetForWrite(true)) + require.Error(err) + assert.Equal(0, rowsUpdated) + + after := newPlugin.testClonePlugin() + err = w.LookupById(context.Background(), after) + require.NoError(err) + + assert.True(proto.Equal(orig, after)) + }) + } +} + +func (c *plugin) testClonePlugin() *plugin { + cp := proto.Clone(c.Plugin) + return &plugin{ + Plugin: cp.(*store.Plugin), + } +} diff --git a/internal/plugin/store/plugin.pb.go b/internal/plugin/store/plugin.pb.go new file mode 100644 index 000000000..2514d8a1c --- /dev/null +++ b/internal/plugin/store/plugin.pb.go @@ -0,0 +1,182 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.17.3 +// source: controller/storage/plugin/store/v1/plugin.proto + +// Package store provides protobufs for storing types in the host package. + +package store + +import ( + _ "github.com/hashicorp/boundary/internal/db/timestamp" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a typeless plugin. In practice this should never be used directly. +// This is included for testing purposes only. +type Plugin struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // public_id is a surrogate key suitable for use in a public API. + // @inject_tag: `gorm:"primary_key"` + PublicId string `protobuf:"bytes,10,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"` + // The scope_id of the owning scope and must be set. + // @inject_tag: `gorm:"not_null"` + ScopeId string `protobuf:"bytes,20,opt,name=scope_id,json=scopeId,proto3" json:"scope_id,omitempty" gorm:"not_null"` + // name is optional. If set, it must be unique within scope_id. + // @inject_tag: `gorm:"default:null"` + Name string `protobuf:"bytes,30,opt,name=name,proto3" json:"name,omitempty" gorm:"default:null"` +} + +func (x *Plugin) Reset() { + *x = Plugin{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_plugin_store_v1_plugin_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Plugin) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Plugin) ProtoMessage() {} + +func (x *Plugin) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_plugin_store_v1_plugin_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Plugin.ProtoReflect.Descriptor instead. +func (*Plugin) Descriptor() ([]byte, []int) { + return file_controller_storage_plugin_store_v1_plugin_proto_rawDescGZIP(), []int{0} +} + +func (x *Plugin) GetPublicId() string { + if x != nil { + return x.PublicId + } + return "" +} + +func (x *Plugin) GetScopeId() string { + if x != nil { + return x.ScopeId + } + return "" +} + +func (x *Plugin) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +var File_controller_storage_plugin_store_v1_plugin_proto protoreflect.FileDescriptor + +var file_controller_storage_plugin_store_v1_plugin_proto_rawDesc = []byte{ + 0x0a, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f, + 0x72, 0x61, 0x67, 0x65, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x73, 0x74, 0x6f, 0x72, + 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x12, 0x22, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, + 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x73, 0x74, 0x6f, + 0x72, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, + 0x72, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x54, 0x0a, 0x06, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, + 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x19, 0x0a, + 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x3b, 0x5a, 0x39, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, + 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x73, 0x74, + 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, +} + +var ( + file_controller_storage_plugin_store_v1_plugin_proto_rawDescOnce sync.Once + file_controller_storage_plugin_store_v1_plugin_proto_rawDescData = file_controller_storage_plugin_store_v1_plugin_proto_rawDesc +) + +func file_controller_storage_plugin_store_v1_plugin_proto_rawDescGZIP() []byte { + file_controller_storage_plugin_store_v1_plugin_proto_rawDescOnce.Do(func() { + file_controller_storage_plugin_store_v1_plugin_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_storage_plugin_store_v1_plugin_proto_rawDescData) + }) + return file_controller_storage_plugin_store_v1_plugin_proto_rawDescData +} + +var file_controller_storage_plugin_store_v1_plugin_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_controller_storage_plugin_store_v1_plugin_proto_goTypes = []interface{}{ + (*Plugin)(nil), // 0: controller.storage.plugin.store.v1.Plugin +} +var file_controller_storage_plugin_store_v1_plugin_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_controller_storage_plugin_store_v1_plugin_proto_init() } +func file_controller_storage_plugin_store_v1_plugin_proto_init() { + if File_controller_storage_plugin_store_v1_plugin_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_controller_storage_plugin_store_v1_plugin_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Plugin); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_controller_storage_plugin_store_v1_plugin_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_controller_storage_plugin_store_v1_plugin_proto_goTypes, + DependencyIndexes: file_controller_storage_plugin_store_v1_plugin_proto_depIdxs, + MessageInfos: file_controller_storage_plugin_store_v1_plugin_proto_msgTypes, + }.Build() + File_controller_storage_plugin_store_v1_plugin_proto = out.File + file_controller_storage_plugin_store_v1_plugin_proto_rawDesc = nil + file_controller_storage_plugin_store_v1_plugin_proto_goTypes = nil + file_controller_storage_plugin_store_v1_plugin_proto_depIdxs = nil +} diff --git a/internal/plugin/testing.go b/internal/plugin/testing.go new file mode 100644 index 000000000..01b6efca5 --- /dev/null +++ b/internal/plugin/testing.go @@ -0,0 +1,54 @@ +package plugin + +import ( + "context" + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/plugin/store" + "github.com/hashicorp/boundary/internal/types/scope" + "github.com/stretchr/testify/require" +) + +// A typeless plugin used for tests. +type plugin struct { + *store.Plugin + tableName string `gorm:"-"` +} + +// newPlugin is used in tests and creates a typeless plugin in the global scope. +func newPlugin(name string) *plugin { + p := &plugin{ + Plugin: &store.Plugin{ + ScopeId: scope.Global.String(), + Name: name, + }, + } + return p +} + +// TableName returns the table name for the host plugin. +func (c *plugin) TableName() string { + if c.tableName != "" { + return c.tableName + } + return "plugin" +} + +// SetTableName sets the table name. If the caller attempts to +// set the name to "" the name will be reset to the default name. +func (c *plugin) SetTableName(n string) { + c.tableName = n +} + +func testPlugin(t *testing.T, conn *db.DB, name string) *plugin { + t.Helper() + p := newPlugin(name) + id, err := db.NewPublicId("plg") + require.NoError(t, err) + p.PublicId = id + + w := db.New(conn) + require.NoError(t, w.Create(context.Background(), p)) + return p +} diff --git a/internal/plugin/testing_test.go b/internal/plugin/testing_test.go new file mode 100644 index 000000000..a047dcc0a --- /dev/null +++ b/internal/plugin/testing_test.go @@ -0,0 +1,18 @@ +package plugin + +import ( + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_testPlugin(t *testing.T) { + assert, require := assert.New(t), require.New(t) + conn, _ := db.TestSetup(t, "postgres") + + plg := testPlugin(t, conn, "test") + require.NotNil(plg) + assert.NotEmpty(plg.GetPublicId()) +} diff --git a/internal/proto/local/controller/api/resources/hostcatalogs/v1/host_catalog.proto b/internal/proto/local/controller/api/resources/hostcatalogs/v1/host_catalog.proto index 22f1c5d9f..c2c8f917b 100644 --- a/internal/proto/local/controller/api/resources/hostcatalogs/v1/host_catalog.proto +++ b/internal/proto/local/controller/api/resources/hostcatalogs/v1/host_catalog.proto @@ -8,6 +8,7 @@ import "google/protobuf/timestamp.proto"; import "google/protobuf/wrappers.proto"; import "google/protobuf/struct.proto"; import "controller/api/resources/scopes/v1/scope.proto"; +import "controller/api/resources/plugins/v1/plugin.proto"; import "controller/custom_options/v1/options.proto"; // HostCatalog manages Hosts and Host Sets @@ -21,6 +22,12 @@ message HostCatalog { // Output only. Scope information for this resource. resources.scopes.v1.ScopeInfo scope = 30; + // The ID of the plugin of which this catalog is created. + string plugin_id = 34 [json_name="plugin_id", (custom_options.v1.generate_sdk_option) = true]; + + // Output only. Plugin information for this resource. + resources.plugins.v1.PluginInfo plugin = 35; + // Optional name for identification purposes. google.protobuf.StringValue name = 40 [(custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = {this:"name" that: "name"}]; @@ -43,6 +50,9 @@ message HostCatalog { // Attributes specific to the catalog type. google.protobuf.Struct attributes = 100 [(custom_options.v1.generate_sdk_option) = true]; + // Secrets specific to the catalog type. These are never output. + google.protobuf.Struct secrets = 110 [(custom_options.v1.generate_sdk_option) = true]; + // Output only. The available actions on this resource for this user. repeated string authorized_actions = 300 [json_name="authorized_actions"]; diff --git a/internal/proto/local/controller/api/resources/hosts/v1/host.proto b/internal/proto/local/controller/api/resources/hosts/v1/host.proto index 64ddd494e..48b37ee90 100644 --- a/internal/proto/local/controller/api/resources/hosts/v1/host.proto +++ b/internal/proto/local/controller/api/resources/hosts/v1/host.proto @@ -8,6 +8,7 @@ import "google/protobuf/struct.proto"; import "google/protobuf/timestamp.proto"; import "google/protobuf/wrappers.proto"; import "controller/api/resources/scopes/v1/scope.proto"; +import "controller/api/resources/plugins/v1/plugin.proto"; import "controller/custom_options/v1/options.proto"; // Host contains all fields related to a Host resource @@ -21,6 +22,9 @@ message Host { // Output only. Scope information for this resource. resources.scopes.v1.ScopeInfo scope = 30; + // Output only. Plugin information for this resource. + resources.plugins.v1.PluginInfo plugin = 35; + // Optional name for identification purposes. google.protobuf.StringValue name = 40 [(custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = {this:"name" that: "name"}]; @@ -46,6 +50,12 @@ message Host { // The attributes that are applicable to the specific Host type. google.protobuf.Struct attributes = 110 [(custom_options.v1.generate_sdk_option) = true]; + // Output only. The list of ip addresses associated with this host. + repeated string ip_addresses = 120; + + // Output only. The list of dns addresses associated with this host. + repeated string dns_names = 130; + // Output only. The available actions on this resource for this user. repeated string authorized_actions = 300 [json_name="authorized_actions"]; } diff --git a/internal/proto/local/controller/api/resources/hostsets/v1/host_set.proto b/internal/proto/local/controller/api/resources/hostsets/v1/host_set.proto index add5017ee..d5baf1535 100644 --- a/internal/proto/local/controller/api/resources/hostsets/v1/host_set.proto +++ b/internal/proto/local/controller/api/resources/hostsets/v1/host_set.proto @@ -8,6 +8,7 @@ import "google/protobuf/struct.proto"; import "google/protobuf/timestamp.proto"; import "google/protobuf/wrappers.proto"; import "controller/api/resources/scopes/v1/scope.proto"; +import "controller/api/resources/plugins/v1/plugin.proto"; import "controller/custom_options/v1/options.proto"; // HostSet is a collection of Hosts created and managed by a Host Catalog @@ -21,6 +22,9 @@ message HostSet { // Output only. Scope information for this resource. resources.scopes.v1.ScopeInfo scope = 30; + // Output only. Plugin information for this resource. + resources.plugins.v1.PluginInfo plugin = 35; + // Optional name for identification purposes. google.protobuf.StringValue name = 40 [(custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = {this:"name" that: "name"}]; @@ -43,8 +47,24 @@ message HostSet { // Output only. A list of Hosts in this Host Set. repeated string host_ids = 100 [json_name="host_ids"]; + // multiple possible endpoints for a host. Preferences are specified by + // "cidr:" or "dns:", specifying which IP + // address or DNS name out of a host's available possibilities should be + // preferred. If no preferences are specified, a value will be chosen from + // among all avialable values using a built-in priority order. May not be + // valid for all plugin types. + repeated string preferred_endpoints = 101 [json_name="preferred_endpoints", (custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = {this:"preferred_endpoints" that: "PreferredEndpoints"}]; + + // An interger number of seconds indicating the amount of time that should + // elapse between syncs of the host set. The interval will be applied to the + // end of the previous sync operation, not the start. Setting to any + // negative value will disable syncing for that host set; setting to zero + // will cause the set to use Boundary's default. The default may change + // between releases. May not be valid for all plugin types. + google.protobuf.Int32Value sync_interval_seconds = 102 [json_name="sync_interval_seconds", (custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = {this:"sync_interval_seconds" that: "SyncIntervalSeconds"}]; + // The attributes that are applicable for the specific Host Set type. - google.protobuf.Struct attributes = 110; + google.protobuf.Struct attributes = 110 [(custom_options.v1.generate_sdk_option) = true]; // Output only. The available actions on this resource for this user. repeated string authorized_actions = 300 [json_name="authorized_actions"]; diff --git a/internal/proto/local/controller/api/resources/plugins/v1/plugin.proto b/internal/proto/local/controller/api/resources/plugins/v1/plugin.proto new file mode 100644 index 000000000..2b1e28b38 --- /dev/null +++ b/internal/proto/local/controller/api/resources/plugins/v1/plugin.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package controller.api.resources.plugins.v1; + +option go_package = "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/plugins;plugins"; + +message PluginInfo { + // Output only. The ID of the Plugin. + string id = 1; + + // Output only. The name of the plugin resource in boundary, if any. + string name = 2; + + // Output only. The description of the plugin in boundary, if any. + string description = 3; +} \ No newline at end of file diff --git a/internal/proto/local/controller/api/services/v1/host_catalog_service.proto b/internal/proto/local/controller/api/services/v1/host_catalog_service.proto index 653f6e98e..db8057d9e 100644 --- a/internal/proto/local/controller/api/services/v1/host_catalog_service.proto +++ b/internal/proto/local/controller/api/services/v1/host_catalog_service.proto @@ -103,6 +103,9 @@ message ListHostCatalogsResponse { message CreateHostCatalogRequest { api.resources.hostcatalogs.v1.HostCatalog item = 1; + // As an alternative to providing the plugin id in the provided HostCatalog, + // this field can be used to lookup the plugin using its name. + string plugin_name = 2 [json_name="plugin_name"]; } message CreateHostCatalogResponse { diff --git a/internal/proto/local/controller/storage/host/plugin/store/v1/host.proto b/internal/proto/local/controller/storage/host/plugin/store/v1/host.proto new file mode 100644 index 000000000..fa40064aa --- /dev/null +++ b/internal/proto/local/controller/storage/host/plugin/store/v1/host.proto @@ -0,0 +1,186 @@ +syntax = "proto3"; + +// Package store provides protobufs for storing types in the plugin host +// package. +package controller.storage.host.plugin.store.v1; +option go_package = "github.com/hashicorp/boundary/internal/host/plugin/store;store"; + +import "google/protobuf/struct.proto"; +import "controller/storage/timestamp/v1/timestamp.proto"; +import "controller/custom_options/v1/options.proto"; + +message HostCatalog { + // public_id is a surrogate key suitable for use in a public API. + // @inject_tag: `gorm:"primary_key"` + string public_id = 1; + + // The create_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp create_time = 2; + + // The update_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp update_time = 3; + + // name is optional. If set, it must be unique within scope_id. + // @inject_tag: `gorm:"default:null"` + string name = 4 [(custom_options.v1.mask_mapping) = {this:"name" that: "name"}]; + + // description is optional. + // @inject_tag: `gorm:"default:null"` + string description = 5 [(custom_options.v1.mask_mapping) = {this:"description" that: "description"}]; + + // The scope_id of the owning scope and must be set. + // @inject_tag: `gorm:"not_null"` + string scope_id = 6; + + // The public id of the plugin this catalog uses. + // @inject_tag: `gorm:"not_null"` + string plugin_id = 7; + + // version allows optimistic locking of the resource + // @inject_tag: `gorm:"default:null"` + uint32 version = 8; + + // attributes is a jsonb formatted field. + // @inject_tag: `gorm:"not_null"` + bytes attributes = 9; +} + +message HostSet { + // public_id is a surrogate key suitable for use in a public API. + // @inject_tag: `gorm:"primary_key"` + string public_id = 1; + + // The create_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp create_time = 2; + + // The update_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp update_time = 3; + + // The last_sync_time is updated every time a host set has been synced. + // @inject_tag: `gorm:"default:null"` + timestamp.v1.Timestamp last_sync_time = 4; + + // The need_sync indicates that a sync needs to happen. + // @inject_tag: `gorm:"not_null"` + bool need_sync = 5; + + // name is optional. If set, it must be unique within + // catalog_id. + // @inject_tag: `gorm:"default:null"` + string name = 6 [(custom_options.v1.mask_mapping) = {this:"name" that: "name"}]; + + // description is optional. + // @inject_tag: `gorm:"default:null"` + string description = 7 [(custom_options.v1.mask_mapping) = {this:"description" that: "description"}]; + + // catalog_id is the public_id of the owning + // plugin_host_catalog and must be set. + // @inject_tag: `gorm:"not_null"` + string catalog_id = 8; + + // version allows optimistic locking of the resource + // @inject_tag: `gorm:"default:null"` + uint32 version = 9; + + // attributes is a byte field containing marshaled JSON data + // @inject_tag: `gorm:"not_null"` + bytes attributes = 10; + + // preferred_endpoints stores string preference values + // @inject_tag: `gorm:"-"` + repeated string preferred_endpoints = 11 [(custom_options.v1.mask_mapping) = {this:"PreferredEndpoints" that: "preferred_endpoints"}]; + + // Sync interval is a value representing a duration in seconds + // @inject_tag: `gorm:"default:null"` + int32 sync_interval_seconds = 12 [(custom_options.v1.mask_mapping) = {this: "SyncIntervalSeconds" that: "sync_interval_seconds"}]; +} + +message HostCatalogSecret { + // catalog_id is the public id of the catalog containing this secret. + // @inject_tag: `gorm:"primary_key"` + string catalog_id = 1; + + // The create_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp create_time = 2; + + // The update_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp update_time = 3; + + // attributes is the plain-text of the attribute data. We are not storing + // this plain-text value in the database. + // @inject_tag: `gorm:"-" wrapping:"pt,secret_data"` + bytes secret = 4; + + // ct_attributes is the ciphertext of the attribute data stored in the db. + // @inject_tag: `gorm:"column:secret;not_null" wrapping:"ct,secret_data"` + bytes ct_secret = 5; + + // The key_id of the kms database key used for encrypting this entry. + // It must be set. + // @inject_tag: `gorm:"not_null"` + string key_id = 6; +} + +// TODO: Add a field which tracks if the host in cache should be considered +// invalid and fall back to the plugin provided data. +message Host { + // public_id is a surrogate key suitable for use in a public API. + // @inject_tag: `gorm:"primary_key"` + string public_id = 1; + + // external_id is an id provided by the plugin. + // @inject_tag: `gorm:"not_null"` + string external_id = 2; + + // The create_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp create_time = 3; + + // The update_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp update_time = 4; + + // name is optional. If set, it must be unique within + // catalog_id. + // @inject_tag: `gorm:"default:null"` + string name = 5; + + // description is optional. + // @inject_tag: `gorm:"default:null"` + string description = 6; + + // catalog_id is the public_id of the owning + // plugin_host_catalog and must be set. + // @inject_tag: `gorm:"not_null"` + string catalog_id = 7; + + // @inject_tag: `gorm:"-"` + uint32 version = 8; + + // ip_addresses are the ip addresses associated with this host and will + // be persisted in the db through the HostAddress message. + // @inject_tag: `gorm:"-"` + repeated string ip_addresses = 9; + + // dns_names are the dns names associated with this host and will + // be persisted in the db through the HostAddress message. + // @inject_tag: `gorm:"-"` + repeated string dns_names = 10; +} + +message HostSetMember { + // @inject_tag: `gorm:"primary_key"` + string host_id = 1; + + // @inject_tag: `gorm:"primary_key"` + string set_id = 2; + + // @inject_tag: `gorm:"default:null"` + string catalog_id = 3; +} \ No newline at end of file diff --git a/internal/proto/local/controller/storage/host/static/store/v1/static.proto b/internal/proto/local/controller/storage/host/static/store/v1/static.proto index 13895f7b5..8eea0f87e 100644 --- a/internal/proto/local/controller/storage/host/static/store/v1/static.proto +++ b/internal/proto/local/controller/storage/host/static/store/v1/static.proto @@ -117,3 +117,11 @@ message HostSetMember { // @inject_tag: `gorm:"default:null"` string catalog_id = 3; } + +// These fields are not implemented yet on the host set. They are captured +// here for the purpose of identifying the mask maps which are on the top level +// api set resource but aren't present in the static host set storage. +message UnimplementedSetFields { + repeated string preferred_endpoints = 11 [(custom_options.v1.mask_mapping) = {this:"PreferredEndpoints" that: "preferred_endpoints"}]; + int32 sync_interval_seconds = 22 [(custom_options.v1.mask_mapping) = {this: "SyncIntervalSeconds" that: "sync_interval_seconds"}]; +} \ No newline at end of file diff --git a/internal/proto/local/controller/storage/host/store/v1/host.proto b/internal/proto/local/controller/storage/host/store/v1/host.proto index 31a75bf05..0f89ebaed 100644 --- a/internal/proto/local/controller/storage/host/store/v1/host.proto +++ b/internal/proto/local/controller/storage/host/store/v1/host.proto @@ -33,3 +33,33 @@ message Set { // @inject_tag: `gorm:"not_null"` string catalog_id = 2; } + +message PreferredEndpoint { + // The ID of the host set this belongs to. + // @inject_tag: `gorm:"primary_key"` + string host_set_id = 1; + + // The priority order, zero-indexed + // @inject_tag: `gorm:"primary_key"` + uint32 priority = 2; + + // The string text of the preference condition + // @inject_tag: `gorm:"not_null"` + string condition = 3; +} + +message IpAddress { + // @inject_tag: `gorm:"primary_key"` + string host_id = 1; + + // @inject_tag: `gorm:"primary_key"` + string address = 2; +} + +message DnsName { + // @inject_tag: `gorm:"primary_key"` + string host_id = 1; + + // @inject_tag: `gorm:"primary_key"` + string name = 2; +} \ No newline at end of file diff --git a/internal/proto/local/controller/storage/plugin/host/store/v1/plugin.proto b/internal/proto/local/controller/storage/plugin/host/store/v1/plugin.proto new file mode 100644 index 000000000..3d7f54ba2 --- /dev/null +++ b/internal/proto/local/controller/storage/plugin/host/store/v1/plugin.proto @@ -0,0 +1,37 @@ +syntax = "proto3"; + +// Package store provides protobufs for storing types in the host package. +package controller.storage.plugin.host.store.v1; +option go_package = "github.com/hashicorp/boundary/internal/plugin/host/store;store"; + +import "controller/storage/timestamp/v1/timestamp.proto"; +import "controller/custom_options/v1/options.proto"; + +message Plugin { + // public_id is a surrogate key suitable for use in a public API. + // @inject_tag: `gorm:"primary_key"` + string public_id = 10; + + // The create_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp create_time = 20; + + // The update_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp update_time = 30; + + // name is optional. If set, it must be unique within scope_id. + // @inject_tag: `gorm:"default:null"` + string name = 4 [(custom_options.v1.mask_mapping) = {this:"Name" that: "name"}]; + + // description is optional. + // @inject_tag: `gorm:"default:null"` + string description = 50 [(custom_options.v1.mask_mapping) = {this:"Description" that: "description"}]; + + // The scope_id of the owning scope and must be set. + // @inject_tag: `gorm:"not_null"` + string scope_id = 60; + + // @inject_tag: `gorm:"default:null"` + uint32 version = 70; +} \ No newline at end of file diff --git a/internal/proto/local/controller/storage/plugin/store/v1/plugin.proto b/internal/proto/local/controller/storage/plugin/store/v1/plugin.proto new file mode 100644 index 000000000..b58884b97 --- /dev/null +++ b/internal/proto/local/controller/storage/plugin/store/v1/plugin.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +// Package store provides protobufs for storing types in the host package. +package controller.storage.plugin.store.v1; +option go_package = "github.com/hashicorp/boundary/internal/plugin/store;store"; + +import "controller/storage/timestamp/v1/timestamp.proto"; + +// This is a typeless plugin. In practice this should never be used directly. +// This is included for testing purposes only. +message Plugin { + // public_id is a surrogate key suitable for use in a public API. + // @inject_tag: `gorm:"primary_key"` + string public_id = 10; + + // The scope_id of the owning scope and must be set. + // @inject_tag: `gorm:"not_null"` + string scope_id = 20; + + // name is optional. If set, it must be unique within scope_id. + // @inject_tag: `gorm:"default:null"` + string name = 30; +} diff --git a/internal/proto/local/plugin/v1/host_plugin_service.proto b/internal/proto/local/plugin/v1/host_plugin_service.proto new file mode 100644 index 000000000..e016c4406 --- /dev/null +++ b/internal/proto/local/plugin/v1/host_plugin_service.proto @@ -0,0 +1,188 @@ +syntax = "proto3"; + +package plugin.v1; + +option go_package = "github.com/hashicorp/boundary/sdk/pbs/plugin;plugin"; + +import "google/protobuf/struct.proto"; + +import "controller/api/resources/hostcatalogs/v1/host_catalog.proto"; +import "controller/api/resources/hostsets/v1/host_set.proto"; +import "controller/api/resources/hosts/v1/host.proto"; + +// HostPluginService describes the service for host plugins. +service HostPluginService { + // OnCreateCatalog is a hook that runs when a host catalog is + // created. + rpc OnCreateCatalog(OnCreateCatalogRequest) returns (OnCreateCatalogResponse); + + // OnUpdateCatalog is a hook that runs when a host catalog is + // updated. + rpc OnUpdateCatalog(OnUpdateCatalogRequest) returns (OnUpdateCatalogResponse); + + // OnDeleteCatalog is a hook that runs when a host catalog is + // deleted. + rpc OnDeleteCatalog(OnDeleteCatalogRequest) returns (OnDeleteCatalogResponse); + + // OnCreateSet is a hook that runs when a host set is created. + rpc OnCreateSet(OnCreateSetRequest) returns (OnCreateSetResponse); + + // OnUpdateSet is a hook that runs when a host set is updated. + rpc OnUpdateSet(OnUpdateSetRequest) returns (OnUpdateSetResponse); + + // OnDeleteSet is a hook that runs when a host set is deleted. + rpc OnDeleteSet(OnDeleteSetRequest) returns (OnDeleteSetResponse); + + // ListHosts looks up all the hosts in the provided host sets. + rpc ListHosts(ListHostsRequest) returns (ListHostsResponse); +} + +message OnCreateCatalogRequest { + // The host catalog to create. The request may contain optional + // secret data to help authenticate the request against a cloud + // API. + controller.api.resources.hostcatalogs.v1.HostCatalog catalog = 10; +} + +message OnCreateCatalogResponse { + // Secret data to persist encrypted within Boundary. This should be used to + // store authentication data and other necessary configuration to be used in + // later hooks and calls. Returning an error from the call will cause this + // data to not be persisted. If this is nil, nothing is written. + HostCatalogPersisted persisted = 10; +} + +message OnUpdateCatalogRequest { + // The existing state of the catalog. + controller.api.resources.hostcatalogs.v1.HostCatalog current_catalog = 10; + + // The requested new state of the catalog. This field may contain optional + // secret data that may have been updated from old authentication data + // contained within the persisted state. + controller.api.resources.hostcatalogs.v1.HostCatalog new_catalog = 20; + + // The existing persisted secret data. + HostCatalogPersisted persisted = 30; +} + +message OnUpdateCatalogResponse { + // The updated secret data to persist encrypted within Boundary. If an error + // is returned, the update of the persisted data is aborted. If this is nil, + // no changes are written. To remove all values, simply return an allocated + // but empty map. + HostCatalogPersisted persisted = 10; +} + +message OnDeleteCatalogRequest { + // The existing state of the catalog to delete. + controller.api.resources.hostcatalogs.v1.HostCatalog catalog = 10; + + // The host sets contained in the catalog being deleted. + repeated controller.api.resources.hostsets.v1.HostSet sets = 20; + + // The existing persisted secret data. + HostCatalogPersisted persisted = 30; +} + +message OnDeleteCatalogResponse {} + +message OnCreateSetRequest { + // The host catalog that the set belongs to. + controller.api.resources.hostcatalogs.v1.HostCatalog catalog = 10; + + // The host set to create. + controller.api.resources.hostsets.v1.HostSet set = 20; + + // The persisted data for the host catalog that the set belongs to. + HostCatalogPersisted persisted = 30; +} + +message OnCreateSetResponse {} + +message OnUpdateSetRequest { + // The host catalog that the set belongs to. + controller.api.resources.hostcatalogs.v1.HostCatalog catalog = 10; + + // The existing state of the host set. + controller.api.resources.hostsets.v1.HostSet current_set = 20; + + // The requested new state of the host set. + controller.api.resources.hostsets.v1.HostSet new_set = 30; + + // The persisted data for the host catalog that the set belongs to. + HostCatalogPersisted persisted = 40; +} + +message OnUpdateSetResponse {} + +message OnDeleteSetRequest { + // The host catalog that the set belongs to. + controller.api.resources.hostcatalogs.v1.HostCatalog catalog = 10; + + // The host set to delete. + controller.api.resources.hostsets.v1.HostSet set = 20; + + // The persisted data for the host catalog that the set belongs to. + HostCatalogPersisted persisted = 30; +} + +message OnDeleteSetResponse {} + +message ListHostsRequest { + // The host catalog that the supplied host sets belong to. + controller.api.resources.hostcatalogs.v1.HostCatalog catalog = 10; + + // The host sets to look up hosts for. + repeated controller.api.resources.hostsets.v1.HostSet sets = 20; + + // The persisted data for the host catalog that the supplied host + // sets belong to. + HostCatalogPersisted persisted = 30; +} + +message ListHostsResponse { + // The hosts to return. + repeated ListHostsResponseHost hosts = 10; +} + +message ListHostsResponseHost { + // Required. A stable identifier for this host. This field is used + // to generate a stable ID for the host within Boundary and is + // included in audit logs. It should be set to something unique and + // useful, ie: a compute instance ID. + string external_id = 10; + + // If supplied, will be set as the managed name of the host + string name = 20; + + // If supplied, will be set as the managed description of the host + string description = 30; + + // Required. Any and all known IP addresses for the host. + repeated string ip_addresses = 40; + + // Required. Any and all known DNS names for the host. + repeated string dns_names = 50; + + // Required. The host set IDs that match this host, out of the host sets + // sent in the request. + repeated string set_ids = 60; + + // Optional. Provider-specific metadata that is applicable to this + // host. Example: host descriptions, tags, alternate network + // addresses, etc. + google.protobuf.Struct attributes = 100; +} + +// HostCatalogPersisted represents state persisted between host catalog calls. +// Its intended purpose is to store authentication data required by the plugin +// to make calls to its respective cloud API. +// +// The secrets stored in this message are encrypted at-rest by Boundary and +// never returned to the end user. +// +// TODO: Add a size limit to this data before we export the plugin SDK. +message HostCatalogPersisted { + // The persisted secrets. + google.protobuf.Struct secrets = 100; +} diff --git a/internal/scheduler/job/additional_verification_test.go b/internal/scheduler/job/additional_verification_test.go index 7d5474e4a..40337ee2c 100644 --- a/internal/scheduler/job/additional_verification_test.go +++ b/internal/scheduler/job/additional_verification_test.go @@ -122,7 +122,7 @@ func TestPlugin(t *testing.T) { // Create test plugin id testPluginId := "pi_test1234" - numRows, err := rw.Exec(context.Background(), "insert into plugin(public_id) values (?)", []interface{}{testPluginId}) + numRows, err := rw.Exec(context.Background(), "insert into plugin(public_id, scope_id) values (?, 'global')", []interface{}{testPluginId}) require.NoError(err) assert.Equal(1, numRows) @@ -155,7 +155,7 @@ func TestPlugin(t *testing.T) { // Create test plugin id testPluginId := "pi_test1234" - numRows, err := rw.Exec(context.Background(), "insert into plugin(public_id) values (?)", []interface{}{testPluginId}) + numRows, err := rw.Exec(context.Background(), "insert into plugin(public_id, scope_id) values (?, 'global')", []interface{}{testPluginId}) assert.NoError(err) assert.Equal(1, numRows) @@ -178,7 +178,7 @@ func TestPlugin(t *testing.T) { // Create test plugin id testPluginId := "pi_test1234" - numRows, err := rw.Exec(context.Background(), "insert into plugin(public_id) values (?)", []interface{}{testPluginId}) + numRows, err := rw.Exec(context.Background(), "insert into plugin(public_id, scope_id) values (?, 'global')", []interface{}{testPluginId}) assert.NoError(err) assert.Equal(1, numRows) diff --git a/internal/servers/controller/common/common.go b/internal/servers/controller/common/common.go index 1c07bfa68..78a9f56eb 100644 --- a/internal/servers/controller/common/common.go +++ b/internal/servers/controller/common/common.go @@ -4,8 +4,10 @@ import ( "github.com/hashicorp/boundary/internal/auth/oidc" "github.com/hashicorp/boundary/internal/auth/password" "github.com/hashicorp/boundary/internal/credential/vault" + pluginhost "github.com/hashicorp/boundary/internal/host/plugin" "github.com/hashicorp/boundary/internal/host/static" "github.com/hashicorp/boundary/internal/iam" + hostplugin "github.com/hashicorp/boundary/internal/plugin/host" "github.com/hashicorp/boundary/internal/servers" "github.com/hashicorp/boundary/internal/session" "github.com/hashicorp/boundary/internal/target" @@ -19,6 +21,8 @@ type ( PasswordAuthRepoFactory func() (*password.Repository, error) ServersRepoFactory func() (*servers.Repository, error) StaticRepoFactory func() (*static.Repository, error) + PluginHostRepoFactory func() (*pluginhost.Repository, error) + HostPluginRepoFactory func() (*hostplugin.Repository, error) SessionRepoFactory func() (*session.Repository, error) TargetRepoFactory func() (*target.Repository, error) ) diff --git a/internal/servers/controller/controller.go b/internal/servers/controller/controller.go index e1071ac93..ec6d74911 100644 --- a/internal/servers/controller/controller.go +++ b/internal/servers/controller/controller.go @@ -14,10 +14,13 @@ import ( "github.com/hashicorp/boundary/internal/cmd/config" "github.com/hashicorp/boundary/internal/credential/vault" "github.com/hashicorp/boundary/internal/db" + pluginhost "github.com/hashicorp/boundary/internal/host/plugin" "github.com/hashicorp/boundary/internal/host/static" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/observability/event" + "github.com/hashicorp/boundary/internal/plugin/host" + hostplugin "github.com/hashicorp/boundary/internal/plugin/host" "github.com/hashicorp/boundary/internal/scheduler" "github.com/hashicorp/boundary/internal/scheduler/job" "github.com/hashicorp/boundary/internal/servers" @@ -25,6 +28,9 @@ import ( "github.com/hashicorp/boundary/internal/session" "github.com/hashicorp/boundary/internal/target" "github.com/hashicorp/boundary/internal/types/scope" + host_plugin_assets "github.com/hashicorp/boundary/plugins/host" + "github.com/hashicorp/boundary/sdk/pbs/plugin" + external_host_plugins "github.com/hashicorp/boundary/sdk/plugins/host" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-secure-stdlib/mlock" ua "go.uber.org/atomic" @@ -62,6 +68,8 @@ type Controller struct { ServersRepoFn common.ServersRepoFactory SessionRepoFn common.SessionRepoFactory StaticHostRepoFn common.StaticRepoFactory + PluginHostRepoFn common.PluginHostRepoFactory + HostPluginRepoFn common.HostPluginRepoFactory TargetRepoFn common.TargetRepoFactory scheduler *scheduler.Scheduler @@ -111,6 +119,38 @@ func New(ctx context.Context, conf *Config) (*Controller, error) { } } + azureSvcClient, azureCleanup, err := external_host_plugins.CreateHostPlugin( + ctx, + "azure", + external_host_plugins.WithHostPluginsFilesystem("boundary-plugin-host-", host_plugin_assets.FileSystem()), + external_host_plugins.WithHostPluginExecutionDir(conf.RawConfig.Plugins.ExecutionDir), + external_host_plugins.WithLogger(hclog.NewNullLogger())) + if err != nil { + return nil, fmt.Errorf("error creating azure host plugin: %w", err) + } + conf.ShutdownFuncs = append(conf.ShutdownFuncs, azureCleanup) + if _, err := conf.RegisterHostPlugin(ctx, "azure", azureSvcClient, hostplugin.WithDescription("Built-in Azure host plugin")); err != nil { + return nil, fmt.Errorf("error registering azure host plugin: %w", err) + } + + awsSvcClient, awsCleanup, err := external_host_plugins.CreateHostPlugin( + ctx, + "aws", + external_host_plugins.WithHostPluginsFilesystem("boundary-plugin-host-", host_plugin_assets.FileSystem()), + external_host_plugins.WithHostPluginExecutionDir(conf.RawConfig.Plugins.ExecutionDir), + external_host_plugins.WithLogger(hclog.NewNullLogger())) + if err != nil { + return nil, fmt.Errorf("error creating aws host plugin") + } + conf.ShutdownFuncs = append(conf.ShutdownFuncs, awsCleanup) + if _, err := conf.RegisterHostPlugin(ctx, "aws", awsSvcClient, hostplugin.WithDescription("Built-in AWS host plugin")); err != nil { + return nil, fmt.Errorf("error registering aws host plugin: %w", err) + } + + if conf.HostPlugins == nil { + conf.HostPlugins = make(map[string]plugin.HostPluginServiceClient) + } + // Set up repo stuff dbase := db.New(c.conf.Database) kmsRepo, err := kms.NewRepository(dbase, dbase) @@ -143,7 +183,11 @@ func New(ctx context.Context, conf *Config) (*Controller, error) { } // TODO: the RunJobsLimit is temporary until a better fix gets in. This // currently caps the scheduler at running 10 jobs per interval. - c.scheduler, err = scheduler.New(c.conf.RawConfig.Controller.Name, jobRepoFn, scheduler.WithRunJobsLimit(10)) + schedulerOpts := []scheduler.Option{scheduler.WithRunJobsLimit(10)} + if c.conf.RawConfig.Controller.SchedulerRunJobInterval > 0 { + schedulerOpts = append(schedulerOpts, scheduler.WithRunJobsInterval(c.conf.RawConfig.Controller.SchedulerRunJobInterval)) + } + c.scheduler, err = scheduler.New(c.conf.RawConfig.Controller.Name, jobRepoFn, schedulerOpts...) if err != nil { return nil, fmt.Errorf("error creating new scheduler: %w", err) } @@ -153,6 +197,12 @@ func New(ctx context.Context, conf *Config) (*Controller, error) { c.StaticHostRepoFn = func() (*static.Repository, error) { return static.NewRepository(dbase, dbase, c.kms) } + c.PluginHostRepoFn = func() (*pluginhost.Repository, error) { + return pluginhost.NewRepository(dbase, dbase, c.kms, c.scheduler, c.conf.HostPlugins) + } + c.HostPluginRepoFn = func() (*host.Repository, error) { + return host.NewRepository(dbase, dbase, c.kms) + } c.AuthTokenRepoFn = func() (*authtoken.Repository, error) { return authtoken.NewRepository(dbase, dbase, c.kms, authtoken.WithTokenTimeToLiveDuration(c.conf.RawConfig.Controller.AuthTokenTimeToLiveDuration), @@ -227,6 +277,9 @@ func (c *Controller) registerJobs() error { if err := vault.RegisterJobs(c.baseContext, c.scheduler, rw, rw, c.kms); err != nil { return err } + if err := pluginhost.RegisterJobs(c.baseContext, c.scheduler, rw, rw, c.kms, c.conf.HostPlugins); err != nil { + return err + } if err := c.registerSessionCleanupJob(); err != nil { return err diff --git a/internal/servers/controller/handler.go b/internal/servers/controller/handler.go index 1f92de586..8ab4c3b19 100644 --- a/internal/servers/controller/handler.go +++ b/internal/servers/controller/handler.go @@ -79,12 +79,11 @@ func handleGrpcGateway(c *Controller, props HandlerProperties) (http.Handler, er // Register*ServiceHandlerServer methods ignore the passed in ctx. Using it // now however in case this changes in the future. ctx := props.CancelCtx - currentServices := c.gatewayServer.GetServiceInfo() dialOptions := gatewayDialOptions(c.gatewayListener) if _, ok := currentServices[services.HostCatalogService_ServiceDesc.ServiceName]; !ok { - hcs, err := host_catalogs.NewService(c.StaticHostRepoFn, c.IamRepoFn) + hcs, err := host_catalogs.NewService(c.StaticHostRepoFn, c.PluginHostRepoFn, c.HostPluginRepoFn, c.IamRepoFn) if err != nil { return nil, fmt.Errorf("failed to create host catalog handler service: %w", err) } @@ -94,7 +93,7 @@ func handleGrpcGateway(c *Controller, props HandlerProperties) (http.Handler, er } } if _, ok := currentServices[services.HostSetService_ServiceDesc.ServiceName]; !ok { - hss, err := host_sets.NewService(c.StaticHostRepoFn) + hss, err := host_sets.NewService(c.StaticHostRepoFn, c.PluginHostRepoFn) if err != nil { return nil, fmt.Errorf("failed to create host set handler service: %w", err) } @@ -104,7 +103,7 @@ func handleGrpcGateway(c *Controller, props HandlerProperties) (http.Handler, er } } if _, ok := currentServices[services.HostService_ServiceDesc.ServiceName]; !ok { - hs, err := hosts.NewService(c.StaticHostRepoFn) + hs, err := hosts.NewService(c.StaticHostRepoFn, c.PluginHostRepoFn) if err != nil { return nil, fmt.Errorf("failed to create host handler service: %w", err) } @@ -165,11 +164,13 @@ func handleGrpcGateway(c *Controller, props HandlerProperties) (http.Handler, er } if _, ok := currentServices[services.TargetService_ServiceDesc.ServiceName]; !ok { ts, err := targets.NewService( + ctx, c.kms, c.TargetRepoFn, c.IamRepoFn, c.ServersRepoFn, c.SessionRepoFn, + c.PluginHostRepoFn, c.StaticHostRepoFn, c.VaultCredentialRepoFn) if err != nil { diff --git a/internal/servers/controller/handlers/host_catalogs/host_catalog_service.go b/internal/servers/controller/handlers/host_catalogs/host_catalog_service.go index a6e85d9e1..a301b1368 100644 --- a/internal/servers/controller/handlers/host_catalogs/host_catalog_service.go +++ b/internal/servers/controller/handlers/host_catalogs/host_catalog_service.go @@ -8,9 +8,11 @@ import ( "github.com/hashicorp/boundary/internal/errors" pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" "github.com/hashicorp/boundary/internal/host" + "github.com/hashicorp/boundary/internal/host/plugin" "github.com/hashicorp/boundary/internal/host/static" "github.com/hashicorp/boundary/internal/host/static/store" "github.com/hashicorp/boundary/internal/perms" + hostplugin "github.com/hashicorp/boundary/internal/plugin/host" "github.com/hashicorp/boundary/internal/requests" "github.com/hashicorp/boundary/internal/servers/controller/auth" "github.com/hashicorp/boundary/internal/servers/controller/common" @@ -21,8 +23,12 @@ import ( "github.com/hashicorp/boundary/internal/types/action" "github.com/hashicorp/boundary/internal/types/resource" "github.com/hashicorp/boundary/internal/types/scope" + "github.com/hashicorp/boundary/internal/types/subtypes" pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs" + "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/plugins" "google.golang.org/grpc/codes" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/wrapperspb" ) @@ -45,9 +51,17 @@ var ( action.List, } - collectionTypeMap = map[resource.Type]action.ActionSet{ - resource.HostSet: host_sets.CollectionActions, - resource.Host: hosts.CollectionActions, + collectionTypeMap = map[subtypes.Subtype]map[resource.Type]action.ActionSet{ + static.Subtype: { + resource.HostSet: host_sets.CollectionActions, + resource.Host: hosts.CollectionActions, + }, + plugin.Subtype: { + resource.HostSet: host_sets.CollectionActions, + resource.Host: action.ActionSet{ + action.List, + }, + }, } ) @@ -61,23 +75,31 @@ func init() { type Service struct { pbs.UnimplementedHostCatalogServiceServer - staticRepoFn common.StaticRepoFactory - iamRepoFn common.IamRepoFactory + staticRepoFn common.StaticRepoFactory + pluginHostRepoFn common.PluginHostRepoFactory + pluginRepoFn common.HostPluginRepoFactory + iamRepoFn common.IamRepoFactory } var _ pbs.HostCatalogServiceServer = Service{} // NewService returns a host catalog Service which handles host catalog related requests to boundary and uses the provided // repositories for storage and retrieval. -func NewService(repoFn common.StaticRepoFactory, iamRepoFn common.IamRepoFactory) (Service, error) { +func NewService(repoFn common.StaticRepoFactory, pluginHostRepoFn common.PluginHostRepoFactory, hostPluginRepoFn common.HostPluginRepoFactory, iamRepoFn common.IamRepoFactory) (Service, error) { const op = "host_catalogs.NewService" if repoFn == nil { return Service{}, errors.NewDeprecated(errors.InvalidParameter, op, "missing static repository") } + if pluginHostRepoFn == nil { + return Service{}, errors.NewDeprecated(errors.InvalidParameter, op, "missing plugin host repository") + } + if hostPluginRepoFn == nil { + return Service{}, errors.NewDeprecated(errors.InvalidParameter, op, "missing host plugin repository") + } if iamRepoFn == nil { return Service{}, errors.NewDeprecated(errors.InvalidParameter, op, "missing iam repository") } - return Service{staticRepoFn: repoFn, iamRepoFn: iamRepoFn}, nil + return Service{staticRepoFn: repoFn, pluginHostRepoFn: pluginHostRepoFn, pluginRepoFn: hostPluginRepoFn, iamRepoFn: iamRepoFn}, nil } func (s Service) ListHostCatalogs(ctx context.Context, req *pbs.ListHostCatalogsRequest) (*pbs.ListHostCatalogsResponse, error) { @@ -108,11 +130,11 @@ func (s Service) ListHostCatalogs(ctx context.Context, req *pbs.ListHostCatalogs return &pbs.ListHostCatalogsResponse{}, nil } - ul, err := s.listFromRepo(ctx, scopeIds) + items, pluginInfoMap, err := s.listFromRepo(ctx, scopeIds) if err != nil { return nil, err } - if len(ul) == 0 { + if len(items) == 0 { return &pbs.ListHostCatalogsResponse{}, nil } @@ -120,11 +142,11 @@ func (s Service) ListHostCatalogs(ctx context.Context, req *pbs.ListHostCatalogs if err != nil { return nil, err } - finalItems := make([]*pb.HostCatalog, 0, len(ul)) + finalItems := make([]*pb.HostCatalog, 0, len(items)) res := perms.Resource{ Type: resource.HostCatalog, } - for _, item := range ul { + for _, item := range items { res.Id = item.GetPublicId() res.ScopeId = item.GetScopeId() authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.WithResource(&res)).Strings() @@ -142,11 +164,26 @@ func (s Service) ListHostCatalogs(ctx context.Context, req *pbs.ListHostCatalogs outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions)) } if outputFields.Has(globals.AuthorizedCollectionActionsField) { - collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, item.GetPublicId()) - if err != nil { - return nil, err + var subtype subtypes.Subtype + switch item.(type) { + case *static.HostCatalog: + subtype = static.Subtype + case *plugin.HostCatalog: + subtype = plugin.Subtype + } + if subtype != "" { + collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap[subtype], authResults.Scope.Id, item.GetPublicId()) + if err != nil { + return nil, err + } + outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions)) + } + } + switch hc := item.(type) { + case *plugin.HostCatalog: + if plgInfo, ok := pluginInfoMap[hc.GetPluginId()]; ok { + outputOpts = append(outputOpts, handlers.WithPlugin(plgInfo)) } - outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions)) } item, err := toProto(ctx, item, outputOpts...) @@ -172,7 +209,7 @@ func (s Service) GetHostCatalog(ctx context.Context, req *pbs.GetHostCatalogRequ if authResults.Error != nil { return nil, authResults.Error } - hc, err := s.getFromRepo(ctx, req.GetId()) + hc, plg, err := s.getFromRepo(ctx, req.GetId()) if err != nil { return nil, err } @@ -191,11 +228,23 @@ func (s Service) GetHostCatalog(ctx context.Context, req *pbs.GetHostCatalogRequ outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, hc.GetPublicId(), IdActions).Strings())) } if outputFields.Has(globals.AuthorizedCollectionActionsField) { - collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, hc.GetPublicId()) - if err != nil { - return nil, err + var subtype subtypes.Subtype + switch hc.(type) { + case *static.HostCatalog: + subtype = static.Subtype + case *plugin.HostCatalog: + subtype = plugin.Subtype } - outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions)) + if subtype != "" { + collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap[subtype], authResults.Scope.Id, hc.GetPublicId()) + if err != nil { + return nil, err + } + outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions)) + } + } + if plg != nil { + outputOpts = append(outputOpts, handlers.WithPlugin(plg)) } item, err := toProto(ctx, hc, outputOpts...) @@ -217,7 +266,7 @@ func (s Service) CreateHostCatalog(ctx context.Context, req *pbs.CreateHostCatal if authResults.Error != nil { return nil, authResults.Error } - hc, err := s.createInRepo(ctx, authResults.Scope.GetId(), req.GetItem()) + hc, plg, err := s.createInRepo(ctx, authResults.Scope.GetId(), req) if err != nil { return nil, err } @@ -227,7 +276,7 @@ func (s Service) CreateHostCatalog(ctx context.Context, req *pbs.CreateHostCatal return nil, errors.New(ctx, errors.Internal, op, "no request context found") } - outputOpts := make([]handlers.Option, 0, 3) + outputOpts := make([]handlers.Option, 0, 4) outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields)) if outputFields.Has(globals.ScopeField) { outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) @@ -236,11 +285,23 @@ func (s Service) CreateHostCatalog(ctx context.Context, req *pbs.CreateHostCatal outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, hc.GetPublicId(), IdActions).Strings())) } if outputFields.Has(globals.AuthorizedCollectionActionsField) { - collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, hc.GetPublicId()) - if err != nil { - return nil, err + var subtype subtypes.Subtype + switch hc.(type) { + case *static.HostCatalog: + subtype = static.Subtype + case *plugin.HostCatalog: + subtype = plugin.Subtype } - outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions)) + if subtype != "" { + collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap[subtype], authResults.Scope.Id, hc.GetPublicId()) + if err != nil { + return nil, err + } + outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions)) + } + } + if plg != nil { + outputOpts = append(outputOpts, handlers.WithPlugin(plg)) } item, err := toProto(ctx, hc, outputOpts...) @@ -265,7 +326,7 @@ func (s Service) UpdateHostCatalog(ctx context.Context, req *pbs.UpdateHostCatal if authResults.Error != nil { return nil, authResults.Error } - hc, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem()) + hc, plg, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req) if err != nil { return nil, err } @@ -277,6 +338,9 @@ func (s Service) UpdateHostCatalog(ctx context.Context, req *pbs.UpdateHostCatal outputOpts := make([]handlers.Option, 0, 3) outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields)) + if plg != nil { + outputOpts = append(outputOpts, handlers.WithPlugin(plg)) + } if outputFields.Has(globals.ScopeField) { outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) } @@ -284,7 +348,7 @@ func (s Service) UpdateHostCatalog(ctx context.Context, req *pbs.UpdateHostCatal outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, hc.GetPublicId(), IdActions).Strings())) } if outputFields.Has(globals.AuthorizedCollectionActionsField) { - collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, hc.GetPublicId()) + collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap[static.Subtype], authResults.Scope.Id, hc.GetPublicId()) if err != nil { return nil, err } @@ -315,45 +379,78 @@ func (s Service) DeleteHostCatalog(ctx context.Context, req *pbs.DeleteHostCatal return nil, nil } -func (s Service) getFromRepo(ctx context.Context, id string) (*static.HostCatalog, error) { - repo, err := s.staticRepoFn() - if err != nil { - return nil, err +func (s Service) getFromRepo(ctx context.Context, id string) (host.Catalog, *plugins.PluginInfo, error) { + var plg *plugins.PluginInfo + var cat host.Catalog + switch host.SubtypeFromId(id) { + case static.Subtype: + repo, err := s.staticRepoFn() + if err != nil { + return nil, nil, err + } + hc, err := repo.LookupCatalog(ctx, id) + if err != nil { + return nil, nil, err + } + if hc == nil { + return nil, nil, handlers.NotFoundErrorf("Host Catalog %q doesn't exist.", id) + } + cat = hc + case plugin.Subtype: + repo, err := s.pluginHostRepoFn() + if err != nil { + return nil, nil, err + } + hc, hcplg, err := repo.LookupCatalog(ctx, id) + if err != nil { + return nil, nil, err + } + if hc == nil { + return nil, nil, handlers.NotFoundErrorf("Host Catalog %q doesn't exist.", id) + } + cat = hc + plg = toPluginInfo(hcplg) } - hc, err := repo.LookupCatalog(ctx, id) - if err != nil { - return nil, err - } - if hc == nil { - return nil, handlers.NotFoundErrorf("Host Catalog %q doesn't exist.", id) - } - return hc, nil + return cat, plg, nil } -func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*static.HostCatalog, error) { +func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]host.Catalog, map[string]*plugins.PluginInfo, error) { repo, err := s.staticRepoFn() if err != nil { - return nil, err + return nil, nil, err } ul, err := repo.ListCatalogs(ctx, scopeIds) if err != nil { - return nil, err + return nil, nil, err } - return ul, nil + var res []host.Catalog + for _, c := range ul { + res = append(res, c) + } + pluginRepo, err := s.pluginHostRepoFn() + if err != nil { + return nil, nil, err + } + pl, plgs, err := pluginRepo.ListCatalogs(ctx, scopeIds) + if err != nil { + return nil, nil, err + } + for _, c := range pl { + res = append(res, c) + } + pluginsMap := make(map[string]*plugins.PluginInfo, len(plgs)) + for _, plg := range plgs { + pluginsMap[plg.GetPublicId()] = toPluginInfo(plg) + } + + return res, pluginsMap, nil } -func (s Service) createInRepo(ctx context.Context, projId string, item *pb.HostCatalog) (*static.HostCatalog, error) { - const op = "host_catalogs.(Service).createInRepo" - var opts []static.Option - if item.GetName() != nil { - opts = append(opts, static.WithName(item.GetName().GetValue())) - } - if item.GetDescription() != nil { - opts = append(opts, static.WithDescription(item.GetDescription().GetValue())) - } - h, err := static.NewHostCatalog(projId, opts...) +func (s Service) createStaticInRepo(ctx context.Context, projId string, item *pb.HostCatalog) (*static.HostCatalog, error) { + const op = "host_catalogs.(Service).createStaticInRepo" + h, err := toStorageStaticCatalog(ctx, projId, item) if err != nil { - return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to build host catalog for creation")) + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to build catalog for creation")) } repo, err := s.staticRepoFn() if err != nil { @@ -369,20 +466,60 @@ func (s Service) createInRepo(ctx context.Context, projId string, item *pb.HostC return out, nil } -func (s Service) updateInRepo(ctx context.Context, projId, id string, mask []string, item *pb.HostCatalog) (*static.HostCatalog, error) { - const op = "host_catalogs.(Service).updateInRepo" - var opts []static.Option - if desc := item.GetDescription(); desc != nil { - opts = append(opts, static.WithDescription(desc.GetValue())) +func (s Service) createPluginInRepo(ctx context.Context, projId string, req *pbs.CreateHostCatalogRequest) (*plugin.HostCatalog, *plugins.PluginInfo, error) { + const op = "host_catalogs.(Service).createPluginInRepo" + item := req.GetItem() + pluginId := item.GetPluginId() + if pluginId == "" { + plgRepo, err := s.pluginRepoFn() + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + plg, err := plgRepo.LookupPluginByName(ctx, req.GetPluginName()) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + if plg == nil { + return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "plugin with provided name not found") + } + pluginId = plg.GetPublicId() } - if name := item.GetName(); name != nil { - opts = append(opts, static.WithName(name.GetValue())) + h, err := toStoragePluginCatalog(ctx, projId, pluginId, item) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to build catalog for creation")) } - version := item.GetVersion() - h, err := static.NewHostCatalog(projId, opts...) + repo, err := s.pluginHostRepoFn() + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + out, plg, err := repo.CreateCatalog(ctx, h) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create host catalog")) + } + if out == nil { + return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create host catalog but no error returned from repository.") + } + return out, toPluginInfo(plg), nil +} + +func (s Service) createInRepo(ctx context.Context, projId string, req *pbs.CreateHostCatalogRequest) (hc host.Catalog, info *plugins.PluginInfo, err error) { + var plg *plugins.PluginInfo + switch host.SubtypeFromType(req.GetItem().GetType()) { + case static.Subtype: + hc, err = s.createStaticInRepo(ctx, projId, req.GetItem()) + default: + hc, plg, err = s.createPluginInRepo(ctx, projId, req) + } + return hc, plg, err +} + +func (s Service) updateStaticInRepo(ctx context.Context, projId, id string, mask []string, item *pb.HostCatalog) (*static.HostCatalog, error) { + const op = "host_catalogs.(Service).updateStaticInRepo" + h, err := toStorageStaticCatalog(ctx, projId, item) if err != nil { return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to build host catalog for update")) } + version := item.GetVersion() h.PublicId = id dbMask := maskManager.Translate(mask) if len(dbMask) == 0 { @@ -402,15 +539,66 @@ func (s Service) updateInRepo(ctx context.Context, projId, id string, mask []str return out, nil } +func (s Service) updatePluginInRepo(ctx context.Context, projId, id string, mask []string, item *pb.HostCatalog) (*plugin.HostCatalog, *plugins.PluginInfo, error) { + const op = "host_catalogs.(Service).updatePluginInRepo" + h, err := toStoragePluginCatalog(ctx, projId, "", item) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to build host catalog for update")) + } + version := item.GetVersion() + h.PublicId = id + dbMask := maskManager.Translate(mask, "attributes", "secrets") + if len(dbMask) == 0 { + return nil, nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."}) + } + + repo, err := s.pluginHostRepoFn() + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + out, plg, rowsUpdated, err := repo.UpdateCatalog(ctx, h, version, dbMask) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to update host catalog")) + } + if rowsUpdated == 0 { + return nil, nil, handlers.NotFoundErrorf("Host Catalog %q doesn't exist or incorrect version provided.", id) + } + return out, toPluginInfo(plg), nil +} + +func (s Service) updateInRepo(ctx context.Context, projId string, req *pbs.UpdateHostCatalogRequest) (hc host.Catalog, plg *plugins.PluginInfo, err error) { + const op = "host_catalogs.(Service).updateInRepo" + switch host.SubtypeFromId(req.GetId()) { + case static.Subtype: + hc, err = s.updateStaticInRepo(ctx, projId, req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem()) + case plugin.Subtype: + hc, plg, err = s.updatePluginInRepo(ctx, projId, req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem()) + } + return +} + func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) { const op = "host_catalogs.(Service).deleteFromRepo" - repo, err := s.staticRepoFn() - if err != nil { - return false, errors.Wrap(ctx, err, op) - } - rows, err := repo.DeleteCatalog(ctx, id) - if err != nil { - return false, errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete host")) + rows := 0 + switch host.SubtypeFromId(id) { + case static.Subtype: + repo, err := s.staticRepoFn() + if err != nil { + return false, errors.Wrap(ctx, err, op) + } + rows, err = repo.DeleteCatalog(ctx, id) + if err != nil { + return false, errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete host")) + } + case plugin.Subtype: + repo, err := s.pluginHostRepoFn() + if err != nil { + return false, errors.Wrap(ctx, err, op) + } + rows, err = repo.DeleteCatalog(ctx, id) + if err != nil { + return false, errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete host")) + } } return rows > 0, nil } @@ -438,28 +626,60 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type) auth. return res } default: - repo, err := s.staticRepoFn() - if err != nil { - res.Error = err - return res + switch host.SubtypeFromId(id) { + case static.Subtype: + repo, err := s.staticRepoFn() + if err != nil { + res.Error = err + return res + } + cat, err := repo.LookupCatalog(ctx, id) + if err != nil { + res.Error = err + return res + } + if cat == nil { + res.Error = handlers.NotFoundError() + return res + } + parentId = cat.GetScopeId() + opts = append(opts, auth.WithId(id)) + case plugin.Subtype: + repo, err := s.pluginHostRepoFn() + if err != nil { + res.Error = err + return res + } + cat, _, err := repo.LookupCatalog(ctx, id) + if err != nil { + res.Error = err + return res + } + if cat == nil { + res.Error = handlers.NotFoundError() + return res + } + parentId = cat.GetScopeId() + opts = append(opts, auth.WithId(id)) } - cat, err := repo.LookupCatalog(ctx, id) - if err != nil { - res.Error = err - return res - } - if cat == nil { - res.Error = handlers.NotFoundError() - return res - } - parentId = cat.GetScopeId() - opts = append(opts, auth.WithId(id)) } opts = append(opts, auth.WithScopeId(parentId)) return auth.Verify(ctx, opts...) } -func toProto(ctx context.Context, in *static.HostCatalog, opt ...handlers.Option) (*pb.HostCatalog, error) { +func toPluginInfo(plg *hostplugin.Plugin) *plugins.PluginInfo { + if plg == nil { + return nil + } + return &plugins.PluginInfo{ + Id: plg.GetPublicId(), + Name: plg.GetName(), + Description: plg.GetDescription(), + } +} + +func toProto(ctx context.Context, in host.Catalog, opt ...handlers.Option) (*pb.HostCatalog, error) { + const op = "host_catalog_service.toProto" opts := handlers.GetOpts(opt...) if opts.WithOutputFields == nil { return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building auth method proto") @@ -473,8 +693,19 @@ func toProto(ctx context.Context, in *static.HostCatalog, opt ...handlers.Option if outputFields.Has(globals.ScopeIdField) { out.ScopeId = in.GetScopeId() } + if outputFields.Has(globals.PluginIdField) { + switch hc := in.(type) { + case *plugin.HostCatalog: + out.PluginId = hc.GetPluginId() + } + } if outputFields.Has(globals.TypeField) { - out.Type = static.Subtype.String() + switch in.(type) { + case *static.HostCatalog: + out.Type = static.Subtype.String() + case *plugin.HostCatalog: + out.Type = plugin.Subtype.String() + } } if outputFields.Has(globals.DescriptionField) && in.GetDescription() != "" { out.Description = wrapperspb.String(in.GetDescription()) @@ -494,15 +725,69 @@ func toProto(ctx context.Context, in *static.HostCatalog, opt ...handlers.Option if outputFields.Has(globals.ScopeField) { out.Scope = opts.WithScope } + if outputFields.Has(globals.PluginField) { + out.Plugin = opts.WithPlugin + } if outputFields.Has(globals.AuthorizedActionsField) { out.AuthorizedActions = opts.WithAuthorizedActions } if outputFields.Has(globals.AuthorizedCollectionActionsField) { out.AuthorizedCollectionActions = opts.WithAuthorizedCollectionActions } + if outputFields.Has(globals.AttributesField) { + switch h := in.(type) { + case *plugin.HostCatalog: + attrs := &structpb.Struct{} + err := proto.Unmarshal(h.Attributes, attrs) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + if len(attrs.GetFields()) > 0 { + out.Attributes = attrs + } + } + } return &out, nil } +func toStorageStaticCatalog(ctx context.Context, scopeId string, item *pb.HostCatalog) (*static.HostCatalog, error) { + const op = "host_catalog_service.toStorageStaticCatalog" + var opts []static.Option + if name := item.GetName(); name != nil { + opts = append(opts, static.WithName(name.GetValue())) + } + if desc := item.GetDescription(); desc != nil { + opts = append(opts, static.WithDescription(desc.GetValue())) + } + hc, err := static.NewHostCatalog(scopeId, opts...) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to build host catalog")) + } + return hc, nil +} + +func toStoragePluginCatalog(ctx context.Context, scopeId, plgId string, item *pb.HostCatalog) (*plugin.HostCatalog, error) { + const op = "host_catalog_service.toStoragePluginCatalog" + var opts []plugin.Option + if name := item.GetName(); name != nil { + opts = append(opts, plugin.WithName(item.GetName().GetValue())) + } + if desc := item.GetDescription(); desc != nil { + opts = append(opts, plugin.WithDescription(desc.GetValue())) + } + if attrs := item.GetAttributes(); attrs != nil { + opts = append(opts, plugin.WithAttributes(attrs)) + } + if secrets := item.GetSecrets(); secrets != nil { + opts = append(opts, plugin.WithSecrets(secrets)) + } + hc, err := plugin.NewHostCatalog(ctx, scopeId, plgId, opts...) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("Unable to build host set for creation")) + } + return hc, nil +} + // A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce // requirements on the structure of the request. They verify that: // * The path passed in is correctly formatted @@ -511,19 +796,31 @@ func toProto(ctx context.Context, in *static.HostCatalog, opt ...handlers.Option // * The type asserted by the ID and/or field is known // * If relevant, the type derived from the id prefix matches what is claimed by the type field func validateGetRequest(req *pbs.GetHostCatalogRequest) error { - return handlers.ValidateGetRequest(handlers.NoopValidatorFn, req, static.HostCatalogPrefix) + return handlers.ValidateGetRequest(handlers.NoopValidatorFn, req, static.HostCatalogPrefix, plugin.HostCatalogPrefix) } func validateCreateRequest(req *pbs.CreateHostCatalogRequest) error { return handlers.ValidateCreateRequest(req.GetItem(), func() map[string]string { badFields := map[string]string{} if !handlers.ValidId(handlers.Id(req.GetItem().GetScopeId()), scope.Project.Prefix()) { - badFields["scope_id"] = "This field must be a valid project scope id." + badFields[globals.ScopeIdField] = "This field must be a valid project scope id." } switch host.SubtypeFromType(req.GetItem().GetType()) { case static.Subtype: + case plugin.Subtype: + if req.GetItem().GetPlugin() != nil { + badFields[globals.PluginField] = "This is a read only field." + } + if req.GetItem().GetPluginId() == "" && req.GetPluginName() == "" { + badFields[globals.PluginIdField] = "This or plugin name is a required field." + badFields[globals.PluginNameField] = "This or plugin id is a required field." + } + if req.GetItem().GetPluginId() != "" && req.GetPluginName() != "" { + badFields[globals.PluginIdField] = "Can't set the plugin name field along with this field." + badFields[globals.PluginNameField] = "Can't set the plugin id field along with this field." + } default: - badFields["type"] = fmt.Sprintf("This is a required field and must be %q.", static.Subtype.String()) + badFields[globals.TypeField] = fmt.Sprintf("This is a required field and must be either %q or %q.", static.Subtype.String(), plugin.Subtype.String()) } return badFields }) @@ -535,25 +832,35 @@ func validateUpdateRequest(req *pbs.UpdateHostCatalogRequest) error { switch host.SubtypeFromId(req.GetId()) { case static.Subtype: if req.GetItem().GetType() != "" && host.SubtypeFromType(req.GetItem().GetType()) != static.Subtype { - badFields["type"] = "Cannot modify resource type." + badFields[globals.TypeField] = "Cannot modify resource type." + } + if req.GetItem().GetPlugin() != nil { + badFields[globals.PluginField] = "This field is unused for this type of host catalog." + } + case plugin.Subtype: + if req.GetItem().GetType() != "" && host.SubtypeFromType(req.GetItem().GetType()) != plugin.Subtype { + badFields[globals.TypeField] = "Cannot modify resource type." + } + if req.GetItem().GetPlugin() != nil { + badFields[globals.PluginField] = "This is a read only field." } } return badFields - }, static.HostCatalogPrefix) + }, static.HostCatalogPrefix, plugin.HostCatalogPrefix) } func validateDeleteRequest(req *pbs.DeleteHostCatalogRequest) error { - return handlers.ValidateDeleteRequest(handlers.NoopValidatorFn, req, static.HostCatalogPrefix) + return handlers.ValidateDeleteRequest(handlers.NoopValidatorFn, req, static.HostCatalogPrefix, plugin.HostCatalogPrefix) } func validateListRequest(req *pbs.ListHostCatalogsRequest) error { badFields := map[string]string{} if !handlers.ValidId(handlers.Id(req.GetScopeId()), scope.Project.Prefix()) && !req.GetRecursive() { - badFields["scope_id"] = "This field must be a valid project scope ID or the list operation must be recursive." + badFields[globals.ScopeIdField] = "This field must be a valid project scope ID or the list operation must be recursive." } if _, err := handlers.NewFilter(req.GetFilter()); err != nil { - badFields["filter"] = fmt.Sprintf("This field could not be parsed. %v", err) + badFields[globals.FilterField] = fmt.Sprintf("This field could not be parsed. %v", err) } if len(badFields) > 0 { return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields) diff --git a/internal/servers/controller/handlers/host_catalogs/host_catalog_service_test.go b/internal/servers/controller/handlers/host_catalogs/host_catalog_service_test.go index 239747a5a..95863b6be 100644 --- a/internal/servers/controller/handlers/host_catalogs/host_catalog_service_test.go +++ b/internal/servers/controller/handlers/host_catalogs/host_catalog_service_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "sort" "strings" "testing" @@ -11,74 +12,87 @@ import ( "github.com/google/go-cmp/cmp" "github.com/hashicorp/boundary/internal/db" pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" + "github.com/hashicorp/boundary/internal/host/plugin" "github.com/hashicorp/boundary/internal/host/static" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/plugin/host" + "github.com/hashicorp/boundary/internal/scheduler" "github.com/hashicorp/boundary/internal/servers/controller/auth" - "github.com/hashicorp/boundary/internal/servers/controller/common" "github.com/hashicorp/boundary/internal/servers/controller/handlers" "github.com/hashicorp/boundary/internal/servers/controller/handlers/host_catalogs" "github.com/hashicorp/boundary/internal/types/scope" + "github.com/hashicorp/boundary/internal/types/subtypes" pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs" + "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/plugins" scopepb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" "google.golang.org/genproto/protobuf/field_mask" "google.golang.org/grpc/codes" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/testing/protocmp" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -var authorizedCollectionActions = map[string]*structpb.ListValue{ - "host-sets": { - Values: []*structpb.Value{ - structpb.NewStringValue("create"), - structpb.NewStringValue("list"), +var authorizedCollectionActions = map[subtypes.Subtype]map[string]*structpb.ListValue{ + static.Subtype: { + "host-sets": { + Values: []*structpb.Value{ + structpb.NewStringValue("create"), + structpb.NewStringValue("list"), + }, + }, + "hosts": { + Values: []*structpb.Value{ + structpb.NewStringValue("create"), + structpb.NewStringValue("list"), + }, }, }, - "hosts": { - Values: []*structpb.Value{ - structpb.NewStringValue("create"), - structpb.NewStringValue("list"), + plugin.Subtype: { + "host-sets": { + Values: []*structpb.Value{ + structpb.NewStringValue("create"), + structpb.NewStringValue("list"), + }, + }, + "hosts": { + Values: []*structpb.Value{ + structpb.NewStringValue("list"), + }, }, }, } var testAuthorizedActions = []string{"no-op", "read", "update", "delete"} -func createDefaultHostCatalogAndRepo(t *testing.T) (*static.HostCatalog, *iam.Scope, common.StaticRepoFactory, common.IamRepoFactory) { - t.Helper() - require := require.New(t) +func TestGet_Static(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) - - _, pRes := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + sche := scheduler.TestScheduler(t, conn, wrapper) + _, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) rw := db.New(conn) - repoFn := func() (*static.Repository, error) { + repo := func() (*static.Repository, error) { return static.NewRepository(rw, rw, kms) } + pluginHostRepo := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) + } + pluginRepo := func() (*host.Repository, error) { + return host.NewRepository(rw, rw, kms) + } iamRepoFn := func() (*iam.Repository, error) { return iam.TestRepo(t, conn, wrapper), nil } + hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] - hc, err := static.NewHostCatalog(pRes.GetPublicId(), static.WithName("default"), static.WithDescription("default")) - require.NoError(err, "Couldn't get new catalog.") - repo, err := repoFn() - require.NoError(err, "Couldn't create static repostitory") - hcRes, err := repo.CreateCatalog(context.Background(), hc) - require.NoError(err, "Couldn't persist new catalog.") - - return hcRes, pRes, repoFn, iamRepoFn -} - -func TestGet(t *testing.T) { - t.Parallel() - hc, proj, repo, iamRepoFn := createDefaultHostCatalogAndRepo(t) toMerge := &pbs.GetHostCatalogRequest{ Id: hc.GetPublicId(), } @@ -87,13 +101,11 @@ func TestGet(t *testing.T) { Id: hc.GetPublicId(), ScopeId: hc.GetScopeId(), Scope: &scopepb.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: proj.GetParentId()}, - Name: &wrappers.StringValue{Value: hc.GetName()}, - Description: &wrappers.StringValue{Value: hc.GetDescription()}, CreatedTime: hc.CreateTime.GetTimestamp(), UpdatedTime: hc.UpdateTime.GetTimestamp(), Type: "static", AuthorizedActions: testAuthorizedActions, - AuthorizedCollectionActions: authorizedCollectionActions, + AuthorizedCollectionActions: authorizedCollectionActions[static.Subtype], } cases := []struct { @@ -132,7 +144,109 @@ func TestGet(t *testing.T) { req := proto.Clone(toMerge).(*pbs.GetHostCatalogRequest) proto.Merge(req, tc.req) - s, err := host_catalogs.NewService(repo, iamRepoFn) + s, err := host_catalogs.NewService(repo, pluginHostRepo, pluginRepo, iamRepoFn) + require.NoError(err, "Couldn't create a new host catalog service.") + + got, gErr := s.GetHostCatalog(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), req) + if tc.err != nil { + require.Error(gErr) + assert.True(errors.Is(gErr, tc.err), "GetHostCatalog(%+v) got error %v, wanted %v", req, gErr, tc.err) + } + + if tc.res != nil { + tc.res.Item.Version = 1 + } + assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "GetHostCatalog(%q) got response %q, wanted %q", req, got, tc.res) + }) + } +} + +func TestGet_Plugin(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + _, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + + rw := db.New(conn) + repo := func() (*static.Repository, error) { + return static.NewRepository(rw, rw, kms) + } + pluginHostRepo := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) + } + pluginRepo := func() (*host.Repository, error) { + return host.NewRepository(rw, rw, kms) + } + iamRepoFn := func() (*iam.Repository, error) { + return iam.TestRepo(t, conn, wrapper), nil + } + name := "test" + plg := host.TestPlugin(t, conn, name) + hc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) + + toMerge := &pbs.GetHostCatalogRequest{ + Id: hc.GetPublicId(), + } + + pHostCatalog := &pb.HostCatalog{ + Id: hc.GetPublicId(), + ScopeId: hc.GetScopeId(), + Scope: &scopepb.ScopeInfo{ + Id: proj.GetPublicId(), + Type: scope.Project.String(), + ParentScopeId: proj.GetParentId(), + }, + PluginId: plg.GetPublicId(), + Plugin: &plugins.PluginInfo{ + Id: plg.GetPublicId(), + Name: plg.GetName(), + Description: plg.GetDescription(), + }, + CreatedTime: hc.CreateTime.GetTimestamp(), + UpdatedTime: hc.UpdateTime.GetTimestamp(), + Type: plugin.Subtype.String(), + AuthorizedActions: testAuthorizedActions, + AuthorizedCollectionActions: authorizedCollectionActions[plugin.Subtype], + } + + cases := []struct { + name string + req *pbs.GetHostCatalogRequest + res *pbs.GetHostCatalogResponse + err error + }{ + { + name: "Get an Existing HostCatalog", + req: &pbs.GetHostCatalogRequest{Id: hc.GetPublicId()}, + res: &pbs.GetHostCatalogResponse{Item: pHostCatalog}, + }, + { + name: "Get a non existing Host Catalog", + req: &pbs.GetHostCatalogRequest{Id: plugin.HostCatalogPrefix + "_DoesntExis"}, + res: nil, + err: handlers.ApiErrorWithCode(codes.NotFound), + }, + { + name: "Wrong id prefix", + req: &pbs.GetHostCatalogRequest{Id: "j_1234567890"}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "space in id", + req: &pbs.GetHostCatalogRequest{Id: plugin.HostCatalogPrefix + "_1 23456789"}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + req := proto.Clone(toMerge).(*pbs.GetHostCatalogRequest) + proto.Merge(req, tc.req) + + s, err := host_catalogs.NewService(repo, pluginHostRepo, pluginRepo, iamRepoFn) require.NoError(err, "Couldn't create a new host catalog service.") got, gErr := s.GetHostCatalog(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), req) @@ -154,9 +268,16 @@ func TestList(t *testing.T) { rw := db.New(conn) wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { return iam.TestRepo(t, conn, wrapper), nil } + pluginHostRepo := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) + } + pluginRepo := func() (*host.Repository, error) { + return host.NewRepository(rw, rw, kms) + } repoFn := func() (*static.Repository, error) { return static.NewRepository(rw, rw, kms) } @@ -177,10 +298,36 @@ func TestList(t *testing.T) { Version: 1, Type: "static", AuthorizedActions: testAuthorizedActions, - AuthorizedCollectionActions: authorizedCollectionActions, + AuthorizedCollectionActions: authorizedCollectionActions[static.Subtype], }) } + var testPluginCatalogs []*pb.HostCatalog + name := "test" + plg := host.TestPlugin(t, conn, name) + for i := 0; i < 3; i++ { + hc := plugin.TestCatalog(t, conn, pWithCatalogs.GetPublicId(), plg.GetPublicId()) + cat := &pb.HostCatalog{ + Id: hc.GetPublicId(), + ScopeId: hc.GetScopeId(), + CreatedTime: hc.GetCreateTime().GetTimestamp(), + UpdatedTime: hc.GetUpdateTime().GetTimestamp(), + Scope: &scopepb.ScopeInfo{Id: pWithCatalogs.GetPublicId(), Type: scope.Project.String(), ParentScopeId: oWithCatalogs.GetPublicId()}, + PluginId: plg.GetPublicId(), + Plugin: &plugins.PluginInfo{ + Id: plg.GetPublicId(), + Name: plg.GetName(), + Description: plg.GetDescription(), + }, + Version: 1, + Type: plugin.Subtype.String(), + AuthorizedActions: testAuthorizedActions, + AuthorizedCollectionActions: authorizedCollectionActions[plugin.Subtype], + } + wantSomeCatalogs = append(wantSomeCatalogs, cat) + testPluginCatalogs = append(testPluginCatalogs, cat) + } + var wantOtherCatalogs []*pb.HostCatalog for _, hc := range static.TestCatalogs(t, conn, pWithOtherCatalogs.GetPublicId(), 3) { wantOtherCatalogs = append(wantOtherCatalogs, &pb.HostCatalog{ @@ -192,7 +339,30 @@ func TestList(t *testing.T) { Version: 1, Type: "static", AuthorizedActions: testAuthorizedActions, - AuthorizedCollectionActions: authorizedCollectionActions, + AuthorizedCollectionActions: authorizedCollectionActions[static.Subtype], + }) + } + + name = "different" + diffPlg := host.TestPlugin(t, conn, name) + for i := 0; i < 3; i++ { + hc := plugin.TestCatalog(t, conn, pWithOtherCatalogs.GetPublicId(), diffPlg.GetPublicId()) + wantOtherCatalogs = append(wantOtherCatalogs, &pb.HostCatalog{ + Id: hc.GetPublicId(), + ScopeId: hc.GetScopeId(), + CreatedTime: hc.GetCreateTime().GetTimestamp(), + UpdatedTime: hc.GetUpdateTime().GetTimestamp(), + Scope: &scopepb.ScopeInfo{Id: pWithOtherCatalogs.GetPublicId(), Type: scope.Project.String(), ParentScopeId: oWithOtherCatalogs.GetPublicId()}, + PluginId: diffPlg.GetPublicId(), + Plugin: &plugins.PluginInfo{ + Id: diffPlg.GetPublicId(), + Name: diffPlg.GetName(), + Description: diffPlg.GetDescription(), + }, + Version: 1, + Type: plugin.Subtype.String(), + AuthorizedActions: testAuthorizedActions, + AuthorizedCollectionActions: authorizedCollectionActions[plugin.Subtype], }) } @@ -242,6 +412,14 @@ func TestList(t *testing.T) { }, res: &pbs.ListHostCatalogsResponse{Items: wantSomeCatalogs}, }, + { + name: "Filter To Catalog Using Test Plugin", + req: &pbs.ListHostCatalogsRequest{ + ScopeId: scope.Global.String(), Recursive: true, + Filter: `"/item/plugin/name"=="test"`, + }, + res: &pbs.ListHostCatalogsResponse{Items: testPluginCatalogs}, + }, { name: "Filter To No Catalogs", req: &pbs.ListHostCatalogsRequest{ScopeId: pWithCatalogs.GetPublicId(), Filter: `"/item/id"=="doesnt match"`}, @@ -256,7 +434,7 @@ func TestList(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - s, err := host_catalogs.NewService(repoFn, iamRepoFn) + s, err := host_catalogs.NewService(repoFn, pluginHostRepo, pluginRepo, iamRepoFn) require.NoError(err, "Couldn't create new auth_method service.") // Test with non-anon user @@ -266,6 +444,13 @@ func TestList(t *testing.T) { assert.True(errors.Is(gErr, tc.err), "ListHostCatalogs() for scope %q got error %v, wanted %v", tc.req.GetScopeId(), gErr, tc.err) return } + sort.Slice(got.Items, func(i, j int) bool { + return got.Items[i].GetId() < got.Items[j].GetId() + }) + sort.Slice(tc.res.Items, func(i, j int) bool { + return tc.res.Items[i].GetId() < tc.res.Items[j].GetId() + }) + assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListHostCatalogs() for scope %q got response %q, wanted %q", tc.req.GetScopeId(), got, tc.res) // Test with anon user @@ -281,11 +466,30 @@ func TestList(t *testing.T) { } } -func TestDelete(t *testing.T) { +func TestDelete_Static(t *testing.T) { t.Parallel() - hc, proj, repo, iamRepoFn := createDefaultHostCatalogAndRepo(t) + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + _, proj := iam.TestScopes(t, iamRepo) + rw := db.New(conn) + repo := func() (*static.Repository, error) { + return static.NewRepository(rw, rw, kms) + } + pluginHostRepo := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) + } + pluginRepo := func() (*host.Repository, error) { + return host.NewRepository(rw, rw, kms) + } + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] - s, err := host_catalogs.NewService(repo, iamRepoFn) + s, err := host_catalogs.NewService(repo, pluginHostRepo, pluginRepo, iamRepoFn) require.NoError(t, err, "Couldn't create a new host catalog service.") cases := []struct { @@ -332,12 +536,102 @@ func TestDelete(t *testing.T) { } } +func TestDelete_Plugin(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + _, proj := iam.TestScopes(t, iamRepo) + rw := db.New(conn) + repo := func() (*static.Repository, error) { + return static.NewRepository(rw, rw, kms) + } + pluginHostRepo := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) + } + pluginRepo := func() (*host.Repository, error) { + return host.NewRepository(rw, rw, kms) + } + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + plg := host.TestPlugin(t, conn, "test") + hc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) + + s, err := host_catalogs.NewService(repo, pluginHostRepo, pluginRepo, iamRepoFn) + require.NoError(t, err, "Couldn't create a new host catalog service.") + + cases := []struct { + name string + scopeId string + req *pbs.DeleteHostCatalogRequest + res *pbs.DeleteHostCatalogResponse + err error + }{ + { + name: "Delete an Existing HostCatalog", + scopeId: proj.GetPublicId(), + req: &pbs.DeleteHostCatalogRequest{ + Id: hc.GetPublicId(), + }, + }, + { + name: "Delete bad id HostCatalog", + scopeId: proj.GetPublicId(), + req: &pbs.DeleteHostCatalogRequest{ + Id: plugin.HostCatalogPrefix + "_doesntexis", + }, + err: handlers.ApiErrorWithCode(codes.NotFound), + }, + { + name: "Bad HostCatalog Id formatting", + scopeId: proj.GetPublicId(), + req: &pbs.DeleteHostCatalogRequest{ + Id: plugin.HostCatalogPrefix + "_bad_format", + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + got, gErr := s.DeleteHostCatalog(auth.DisabledAuthTestContext(iamRepoFn, tc.scopeId), tc.req) + if tc.err != nil { + require.Error(gErr) + assert.True(errors.Is(gErr, tc.err), "DeleteHostCatalog(%+v) got error %v, wanted %v", tc.req, gErr, tc.err) + } + assert.EqualValuesf(tc.res, got, "DeleteHostCatalog(%q) got response %q, wanted %q", tc.req, got, tc.res) + }) + } +} + func TestDelete_twice(t *testing.T) { t.Parallel() assert, require := assert.New(t), require.New(t) - hc, proj, repo, iamRepoFn := createDefaultHostCatalogAndRepo(t) + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + _, proj := iam.TestScopes(t, iamRepo) + rw := db.New(conn) + repo := func() (*static.Repository, error) { + return static.NewRepository(rw, rw, kms) + } + pluginHostRepo := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) + } + pluginRepo := func() (*host.Repository, error) { + return host.NewRepository(rw, rw, kms) + } + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] - s, err := host_catalogs.NewService(repo, iamRepoFn) + s, err := host_catalogs.NewService(repo, pluginHostRepo, pluginRepo, iamRepoFn) require.NoError(err, "Couldn't create a new host catalog service.") req := &pbs.DeleteHostCatalogRequest{ Id: hc.GetPublicId(), @@ -350,9 +644,28 @@ func TestDelete_twice(t *testing.T) { assert.True(errors.Is(gErr, handlers.ApiErrorWithCode(codes.NotFound)), "Expected permission denied for the second delete.") } -func TestCreate(t *testing.T) { +func TestCreate_Static(t *testing.T) { t.Parallel() - defaultHc, proj, repo, iamRepoFn := createDefaultHostCatalogAndRepo(t) + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + _, proj := iam.TestScopes(t, iamRepo) + rw := db.New(conn) + repo := func() (*static.Repository, error) { + return static.NewRepository(rw, rw, kms) + } + pluginHostRepo := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) + } + pluginRepo := func() (*host.Repository, error) { + return host.NewRepository(rw, rw, kms) + } + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + defaultHc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] defaultHcCreated := defaultHc.GetCreateTime().GetTimestamp().AsTime() toMerge := &pbs.CreateHostCatalogRequest{} @@ -379,7 +692,7 @@ func TestCreate(t *testing.T) { Description: &wrappers.StringValue{Value: "desc"}, Type: "static", AuthorizedActions: testAuthorizedActions, - AuthorizedCollectionActions: authorizedCollectionActions, + AuthorizedCollectionActions: authorizedCollectionActions[static.Subtype], }, }, }, @@ -451,7 +764,7 @@ func TestCreate(t *testing.T) { req := proto.Clone(toMerge).(*pbs.CreateHostCatalogRequest) proto.Merge(req, tc.req) - s, err := host_catalogs.NewService(repo, iamRepoFn) + s, err := host_catalogs.NewService(repo, pluginHostRepo, pluginRepo, iamRepoFn) require.NoError(err, "Failed to create a new host catalog service.") got, gErr := s.CreateHostCatalog(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), req) @@ -483,12 +796,214 @@ func TestCreate(t *testing.T) { } } -func TestUpdate(t *testing.T) { +func TestCreate_Plugin(t *testing.T) { t.Parallel() - hc, proj, repoFn, iamRepoFn := createDefaultHostCatalogAndRepo(t) - tested, err := host_catalogs.NewService(repoFn, iamRepoFn) + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + org, proj := iam.TestScopes(t, iamRepo) + rw := db.New(conn) + repo := func() (*static.Repository, error) { + return static.NewRepository(rw, rw, kms) + } + pluginRepo := func() (*host.Repository, error) { + return host.NewRepository(rw, rw, kms) + } + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + + name := "test" + plg := host.TestPlugin(t, conn, name) + pluginHostRepo := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): plugin.NewWrappingPluginClient(&plugin.TestPluginServer{ + OnCreateCatalogFn: func(ctx context.Context, req *plgpb.OnCreateCatalogRequest) (*plgpb.OnCreateCatalogResponse, error) { + return nil, nil + }, + }), + }) + } + defaultHc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) + defaultHcCreated := defaultHc.GetCreateTime().GetTimestamp().AsTime() + toMerge := &pbs.CreateHostCatalogRequest{} + + cases := []struct { + name string + req *pbs.CreateHostCatalogRequest + res *pbs.CreateHostCatalogResponse + err error + }{ + { + name: "Create a valid HostCatalog", + req: &pbs.CreateHostCatalogRequest{Item: &pb.HostCatalog{ + ScopeId: proj.GetPublicId(), + Name: &wrappers.StringValue{Value: "name"}, + Description: &wrappers.StringValue{Value: "desc"}, + Type: plugin.Subtype.String(), + PluginId: plg.GetPublicId(), + }}, + res: &pbs.CreateHostCatalogResponse{ + Uri: fmt.Sprintf("host-catalogs/%s_", plugin.HostCatalogPrefix), + Item: &pb.HostCatalog{ + ScopeId: proj.GetPublicId(), + Scope: &scopepb.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: proj.GetParentId()}, + PluginId: plg.GetPublicId(), + Plugin: &plugins.PluginInfo{ + Id: plg.GetPublicId(), + Name: plg.GetName(), + Description: plg.GetDescription(), + }, + Name: &wrappers.StringValue{Value: "name"}, + Description: &wrappers.StringValue{Value: "desc"}, + Type: plugin.Subtype.String(), + AuthorizedActions: testAuthorizedActions, + AuthorizedCollectionActions: authorizedCollectionActions[plugin.Subtype], + }, + }, + }, + { + name: "Cant create in org", + req: &pbs.CreateHostCatalogRequest{Item: &pb.HostCatalog{ + ScopeId: org.GetParentId(), + Type: plugin.Subtype.String(), + PluginId: plg.GetPublicId(), + }}, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Cant create in global", + req: &pbs.CreateHostCatalogRequest{Item: &pb.HostCatalog{ + ScopeId: scope.Global.String(), + Type: plugin.Subtype.String(), + PluginId: plg.GetPublicId(), + }}, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Create with unknown type", + req: &pbs.CreateHostCatalogRequest{Item: &pb.HostCatalog{ + ScopeId: proj.GetPublicId(), + PluginId: plg.GetPublicId(), + Type: "thisismadeup", + }}, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Create with no type", + req: &pbs.CreateHostCatalogRequest{Item: &pb.HostCatalog{ + ScopeId: proj.GetPublicId(), + PluginId: plg.GetPublicId(), + }}, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Can't specify Id", + req: &pbs.CreateHostCatalogRequest{Item: &pb.HostCatalog{ + Id: "not allowed to be set", + ScopeId: proj.GetPublicId(), + Type: plugin.Subtype.String(), + PluginId: plg.GetPublicId(), + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Can't specify Created Time", + req: &pbs.CreateHostCatalogRequest{Item: &pb.HostCatalog{ + CreatedTime: timestamppb.Now(), + ScopeId: proj.GetPublicId(), + Type: plugin.Subtype.String(), + PluginId: plg.GetPublicId(), + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Can't specify Update Time", + req: &pbs.CreateHostCatalogRequest{Item: &pb.HostCatalog{ + UpdatedTime: timestamppb.Now(), + ScopeId: proj.GetPublicId(), + Type: plugin.Subtype.String(), + PluginId: plg.GetPublicId(), + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + req := proto.Clone(toMerge).(*pbs.CreateHostCatalogRequest) + proto.Merge(req, tc.req) + + s, err := host_catalogs.NewService(repo, pluginHostRepo, pluginRepo, iamRepoFn) + require.NoError(err, "Failed to create a new host catalog service.") + + got, gErr := s.CreateHostCatalog(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), req) + if tc.err != nil { + require.Error(gErr) + assert.True(errors.Is(gErr, tc.err), "CreateHostCatalog(%+v) got error %v, wanted %v", req, gErr, tc.err) + return + } + require.NoError(gErr) + if got != nil { + assert.Contains(got.GetUri(), tc.res.GetUri()) + assert.True(strings.HasPrefix(got.GetItem().GetId(), fmt.Sprintf("%s_", plugin.HostCatalogPrefix))) + gotCreateTime := got.GetItem().GetCreatedTime().AsTime() + require.NoError(err, "Error converting proto to timestamp.") + gotUpdateTime := got.GetItem().GetUpdatedTime().AsTime() + require.NoError(err, "Error converting proto to timestamp") + // Verify it is a catalog created after the test setup's default catalog + assert.True(gotCreateTime.After(defaultHcCreated), "New catalog should have been created after default catalog. Was created %v, which is after %v", gotCreateTime, defaultHcCreated) + assert.True(gotUpdateTime.After(defaultHcCreated), "New catalog should have been updated after default catalog. Was updated %v, which is after %v", gotUpdateTime, defaultHcCreated) + + // Clear all values which are hard to compare against. + got.Uri, tc.res.Uri = "", "" + got.Item.Id, tc.res.Item.Id = "", "" + got.Item.CreatedTime, got.Item.UpdatedTime, tc.res.Item.CreatedTime, tc.res.Item.UpdatedTime = nil, nil, nil, nil + } + if tc.res != nil { + tc.res.Item.Version = 1 + } + assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "CreateHostCatalog(%q) got response %q, wanted %q", req, got, tc.res) + }) + } +} + +func TestUpdate_Static(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + _, proj := iam.TestScopes(t, iamRepo) + rw := db.New(conn) + repoFn := func() (*static.Repository, error) { + return static.NewRepository(rw, rw, kms) + } + pluginHostRepo := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) + } + pluginRepo := func() (*host.Repository, error) { + return host.NewRepository(rw, rw, kms) + } + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + tested, err := host_catalogs.NewService(repoFn, pluginHostRepo, pluginRepo, iamRepoFn) require.NoError(t, err, "Failed to create a new host catalog service.") + hc, err := static.NewHostCatalog(proj.GetPublicId(), static.WithName("default"), static.WithDescription("default")) + require.NoError(t, err, "Couldn't get new catalog.") + repo, err := repoFn() + require.NoError(t, err, "Couldn't create static repostitory") + hc, err = repo.CreateCatalog(context.Background(), hc) + require.NoError(t, err, "Couldn't persist new catalog.") + var version uint32 = 1 resetHostCatalog := func() { @@ -533,7 +1048,7 @@ func TestUpdate(t *testing.T) { CreatedTime: hc.GetCreateTime().GetTimestamp(), Type: "static", AuthorizedActions: testAuthorizedActions, - AuthorizedCollectionActions: authorizedCollectionActions, + AuthorizedCollectionActions: authorizedCollectionActions[static.Subtype], }, }, }, @@ -559,7 +1074,7 @@ func TestUpdate(t *testing.T) { CreatedTime: hc.GetCreateTime().GetTimestamp(), Type: "static", AuthorizedActions: testAuthorizedActions, - AuthorizedCollectionActions: authorizedCollectionActions, + AuthorizedCollectionActions: authorizedCollectionActions[static.Subtype], }, }, }, @@ -614,7 +1129,7 @@ func TestUpdate(t *testing.T) { CreatedTime: hc.GetCreateTime().GetTimestamp(), Type: "static", AuthorizedActions: testAuthorizedActions, - AuthorizedCollectionActions: authorizedCollectionActions, + AuthorizedCollectionActions: authorizedCollectionActions[static.Subtype], }, }, }, @@ -637,7 +1152,7 @@ func TestUpdate(t *testing.T) { CreatedTime: hc.GetCreateTime().GetTimestamp(), Type: "static", AuthorizedActions: testAuthorizedActions, - AuthorizedCollectionActions: authorizedCollectionActions, + AuthorizedCollectionActions: authorizedCollectionActions[static.Subtype], }, }, }, @@ -662,7 +1177,7 @@ func TestUpdate(t *testing.T) { CreatedTime: hc.GetCreateTime().GetTimestamp(), Type: "static", AuthorizedActions: testAuthorizedActions, - AuthorizedCollectionActions: authorizedCollectionActions, + AuthorizedCollectionActions: authorizedCollectionActions[static.Subtype], }, }, }, @@ -687,7 +1202,7 @@ func TestUpdate(t *testing.T) { CreatedTime: hc.GetCreateTime().GetTimestamp(), Type: "static", AuthorizedActions: testAuthorizedActions, - AuthorizedCollectionActions: authorizedCollectionActions, + AuthorizedCollectionActions: authorizedCollectionActions[static.Subtype], }, }, }, @@ -797,7 +1312,9 @@ func TestUpdate(t *testing.T) { if tc.err != nil { require.Error(gErr) assert.True(errors.Is(gErr, tc.err), "UpdateHostCatalog(%+v) got error %v, wanted %v", req, gErr, tc.err) + return } + require.NoError(gErr) if tc.err == nil { defer resetHostCatalog() @@ -823,3 +1340,303 @@ func TestUpdate(t *testing.T) { }) } } + +func TestUpdate_Plugin(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + _, proj := iam.TestScopes(t, iamRepo) + rw := db.New(conn) + + name := "test" + plg := host.TestPlugin(t, conn, name) + plgm := map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): plugin.NewWrappingPluginClient(plugin.NewLoopbackPlugin()), + } + + repoFn := func() (*static.Repository, error) { + return static.NewRepository(rw, rw, kms) + } + pluginHostRepo := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, plgm) + } + pluginRepo := func() (*host.Repository, error) { + return host.NewRepository(rw, rw, kms) + } + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + tested, err := host_catalogs.NewService(repoFn, pluginHostRepo, pluginRepo, iamRepoFn) + require.NoError(t, err, "Failed to create a new host catalog service.") + + ctx := auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()) + + freshCatalog := func(t *testing.T) *pb.HostCatalog { + attr, err := structpb.NewStruct(map[string]interface{}{ + "foo": "bar", + }) + require.NoError(t, err) + resp, err := tested.CreateHostCatalog(ctx, &pbs.CreateHostCatalogRequest{Item: &pb.HostCatalog{ + ScopeId: proj.GetPublicId(), + PluginId: plg.GetPublicId(), + Name: wrapperspb.String("default"), + Description: wrapperspb.String("default"), + Type: "plugin", + Attributes: attr, + }}) + require.NoError(t, err) + id := resp.GetItem().GetId() + t.Cleanup(func() { + _, err := tested.DeleteHostCatalog(ctx, &pbs.DeleteHostCatalogRequest{Id: id}) + require.NoError(t, err) + }) + return resp.GetItem() + } + + type updateFn func(catalog *pb.HostCatalog) + clearReadOnlyFields := func() updateFn { + return func(c *pb.HostCatalog) { + c.Id = "" + c.Plugin = nil + c.AuthorizedActions = nil + c.AuthorizedCollectionActions = nil + c.CreatedTime = nil + c.UpdatedTime = nil + } + } + updateName := func(i *wrappers.StringValue) updateFn { + return func(c *pb.HostCatalog) { + c.Name = i + } + } + updateDesc := func(i *wrappers.StringValue) updateFn { + return func(c *pb.HostCatalog) { + c.Description = i + } + } + updateAttrs := func(i *structpb.Struct) updateFn { + return func(c *pb.HostCatalog) { + c.Attributes = i + } + } + updateSecrets := func(i *structpb.Struct) updateFn { + return func(c *pb.HostCatalog) { + c.Secrets = i + } + } + + cases := []struct { + name string + masks []string + changes []updateFn + check func(*testing.T, *pb.HostCatalog) + err error + }{ + { + name: "Update an Existing HostCatalog", + masks: []string{"name", "description"}, + changes: []updateFn{ + clearReadOnlyFields(), + updateName(wrapperspb.String("new")), + updateDesc(wrapperspb.String("desc")), + }, + check: func(t *testing.T, in *pb.HostCatalog) { + assert.Equal(t, "new", in.Name.GetValue()) + assert.Equal(t, "desc", in.Description.GetValue()) + }, + }, + { + name: "Update arbitrary attribute for catalog", + masks: []string{"attributes.newkey", "attributes.foo"}, + changes: []updateFn{ + clearReadOnlyFields(), + updateAttrs(func() *structpb.Struct { + attr, err := structpb.NewStruct(map[string]interface{}{ + "newkey": "newvalue", + "foo": nil, + }) + require.NoError(t, err) + return attr + }()), + }, + check: func(t *testing.T, in *pb.HostCatalog) { + assert.Equal(t, map[string]interface{}{"newkey": "newvalue"}, in.GetAttributes().AsMap()) + }, + }, + { + name: "Update secrets", + masks: []string{"secrets.key1", "secrets.key2"}, + changes: []updateFn{ + clearReadOnlyFields(), + updateSecrets(func() *structpb.Struct { + attr, err := structpb.NewStruct(map[string]interface{}{ + "key1": "val1", + }) + require.NoError(t, err) + return attr + }()), + }, + }, + { + name: "Multiple Paths in single string", + masks: []string{"name,description"}, + changes: []updateFn{ + clearReadOnlyFields(), + updateName(wrapperspb.String("new")), + updateDesc(wrapperspb.String("desc")), + }, + check: func(t *testing.T, in *pb.HostCatalog) { + assert.Equal(t, "new", in.Name.GetValue()) + assert.Equal(t, "desc", in.Description.GetValue()) + }, + }, + { + name: "No Update Mask", + changes: []updateFn{ + clearReadOnlyFields(), + updateName(wrapperspb.String("new")), + updateDesc(wrapperspb.String("desc")), + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Empty Path", + masks: []string{}, + changes: []updateFn{ + clearReadOnlyFields(), + updateName(wrapperspb.String("new")), + updateDesc(wrapperspb.String("desc")), + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Only non-existant paths in Mask", + masks: []string{"nonexistant_field"}, + changes: []updateFn{ + clearReadOnlyFields(), + updateName(wrapperspb.String("new")), + updateDesc(wrapperspb.String("desc")), + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Unset Name", + masks: []string{"name"}, + changes: []updateFn{ + clearReadOnlyFields(), + updateName(nil), + updateDesc(wrapperspb.String("ignored")), + }, + check: func(t *testing.T, in *pb.HostCatalog) { + assert.Nil(t, in.Name) + assert.Equal(t, "default", in.Description.GetValue()) + }, + }, + { + name: "Unset Description", + masks: []string{"description"}, + changes: []updateFn{ + clearReadOnlyFields(), + updateName(wrapperspb.String("ignored")), + updateDesc(nil), + }, + check: func(t *testing.T, in *pb.HostCatalog) { + assert.Nil(t, in.Description) + assert.Equal(t, "default", in.Name.GetValue()) + }, + }, + { + name: "Update Only Name", + masks: []string{"name"}, + changes: []updateFn{ + clearReadOnlyFields(), + updateName(wrapperspb.String("updated")), + updateDesc(wrapperspb.String("ignored")), + }, + check: func(t *testing.T, in *pb.HostCatalog) { + assert.Equal(t, "updated", in.Name.GetValue()) + assert.Equal(t, "default", in.Description.GetValue()) + }, + }, + { + name: "Update Only Description", + masks: []string{"description"}, + changes: []updateFn{ + clearReadOnlyFields(), + updateName(wrapperspb.String("ignored")), + updateDesc(wrapperspb.String("updated")), + }, + check: func(t *testing.T, in *pb.HostCatalog) { + assert.Equal(t, "default", in.Name.GetValue()) + assert.Equal(t, "updated", in.Description.GetValue()) + }, + }, + { + name: "Cant change type", + masks: []string{"type"}, + changes: []updateFn{ + clearReadOnlyFields(), + func(c *pb.HostCatalog) { + c.Type = "static" + }, + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Cant specify Updated Time", + masks: []string{"updated_time"}, + changes: []updateFn{ + clearReadOnlyFields(), + func(c *pb.HostCatalog) { + c.UpdatedTime = timestamppb.Now() + }, + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + hc := freshCatalog(t) + + id := hc.GetId() + for _, f := range tc.changes { + f(hc) + } + + req := &pbs.UpdateHostCatalogRequest{ + Id: id, + Item: hc, + UpdateMask: &field_mask.FieldMask{Paths: tc.masks}, + } + + // Test some bad versions + req.Item.Version = 2 + _, gErr := tested.UpdateHostCatalog(ctx, req) + require.Error(gErr) + req.Item.Version = 0 + _, gErr = tested.UpdateHostCatalog(ctx, req) + require.Error(gErr) + req.Item.Version = 1 + + got, gErr := tested.UpdateHostCatalog(ctx, req) + if tc.err != nil { + require.Error(gErr) + assert.True(errors.Is(gErr, tc.err), "UpdateHostCatalog(%+v) got error %v, wanted %v", req, gErr, tc.err) + return + } + require.NoError(gErr) + require.NotNil(got) + + item := got.GetItem() + assert.Equal(uint32(2), item.GetVersion()) + assert.Greater(item.GetUpdatedTime().AsTime().UnixNano(), item.GetCreatedTime().AsTime().UnixNano()) + if tc.check != nil { + tc.check(t, item) + } + }) + } +} diff --git a/internal/servers/controller/handlers/host_sets/host_set_service.go b/internal/servers/controller/handlers/host_sets/host_set_service.go index 0b354560d..9c47f7e65 100644 --- a/internal/servers/controller/handlers/host_sets/host_set_service.go +++ b/internal/servers/controller/handlers/host_sets/host_set_service.go @@ -8,34 +8,50 @@ import ( "github.com/hashicorp/boundary/internal/errors" pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" "github.com/hashicorp/boundary/internal/host" + "github.com/hashicorp/boundary/internal/host/plugin" + plugstore "github.com/hashicorp/boundary/internal/host/plugin/store" "github.com/hashicorp/boundary/internal/host/static" - "github.com/hashicorp/boundary/internal/host/static/store" + staticstore "github.com/hashicorp/boundary/internal/host/static/store" + "github.com/hashicorp/boundary/internal/libs/endpoint" "github.com/hashicorp/boundary/internal/perms" + hostplugin "github.com/hashicorp/boundary/internal/plugin/host" "github.com/hashicorp/boundary/internal/requests" "github.com/hashicorp/boundary/internal/servers/controller/auth" "github.com/hashicorp/boundary/internal/servers/controller/common" "github.com/hashicorp/boundary/internal/servers/controller/handlers" "github.com/hashicorp/boundary/internal/types/action" "github.com/hashicorp/boundary/internal/types/resource" + "github.com/hashicorp/boundary/internal/types/subtypes" pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets" + "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/plugins" "github.com/hashicorp/go-secure-stdlib/strutil" "google.golang.org/grpc/codes" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/wrapperspb" ) var ( - maskManager handlers.MaskManager + maskManager = map[subtypes.Subtype]handlers.MaskManager{} // IdActions contains the set of actions that can be performed on // individual resources - IdActions = action.ActionSet{ - action.NoOp, - action.Read, - action.Update, - action.Delete, - action.AddHosts, - action.SetHosts, - action.RemoveHosts, + idActionsTypeMap = map[subtypes.Subtype]action.ActionSet{ + static.Subtype: { + action.NoOp, + action.Read, + action.Update, + action.Delete, + action.AddHosts, + action.SetHosts, + action.RemoveHosts, + }, + plugin.Subtype: { + action.NoOp, + action.Read, + action.Update, + action.Delete, + }, } // CollectionActions contains the set of actions that can be performed on @@ -48,7 +64,10 @@ var ( func init() { var err error - if maskManager, err = handlers.NewMaskManager(handlers.MaskDestination{&store.HostSet{}}, handlers.MaskSource{&pb.HostSet{}}); err != nil { + if maskManager[static.Subtype], err = handlers.NewMaskManager(handlers.MaskDestination{&staticstore.HostSet{}, &staticstore.UnimplementedSetFields{}}, handlers.MaskSource{&pb.HostSet{}}); err != nil { + panic(err) + } + if maskManager[plugin.Subtype], err = handlers.NewMaskManager(handlers.MaskDestination{&plugstore.HostSet{}}, handlers.MaskSource{&pb.HostSet{}}); err != nil { panic(err) } } @@ -57,21 +76,29 @@ type Service struct { pbs.UnimplementedHostSetServiceServer staticRepoFn common.StaticRepoFactory + pluginRepoFn common.PluginHostRepoFactory } var _ pbs.HostSetServiceServer = Service{} // NewService returns a host set Service which handles host set related requests to boundary and uses the provided // repositories for storage and retrieval. -func NewService(repoFn common.StaticRepoFactory) (Service, error) { +func NewService(staticRepoFn common.StaticRepoFactory, pluginRepoFn common.PluginHostRepoFactory) (Service, error) { const op = "host_sets.NewService" - if repoFn == nil { + if staticRepoFn == nil { return Service{}, errors.NewDeprecated(errors.InvalidParameter, op, "missing static repository") } - return Service{staticRepoFn: repoFn}, nil + if pluginRepoFn == nil { + return Service{}, errors.NewDeprecated(errors.InvalidParameter, op, "missing plugin repository") + } + return Service{staticRepoFn: staticRepoFn, pluginRepoFn: pluginRepoFn}, nil } func (s Service) ListHostSets(ctx context.Context, req *pbs.ListHostSetsRequest) (*pbs.ListHostSetsResponse, error) { + return s.ListHostSetsWithOptions(ctx, req) +} + +func (s Service) ListHostSetsWithOptions(ctx context.Context, req *pbs.ListHostSetsRequest, opt ...host.Option) (*pbs.ListHostSetsResponse, error) { if err := validateListRequest(req); err != nil { return nil, err } @@ -79,7 +106,7 @@ func (s Service) ListHostSets(ctx context.Context, req *pbs.ListHostSetsRequest) if authResults.Error != nil { return nil, authResults.Error } - hl, err := s.listFromRepo(ctx, req.GetHostCatalogId()) + hl, plg, err := s.listFromRepo(ctx, req.GetHostCatalogId(), opt...) if err != nil { return nil, err } @@ -100,7 +127,8 @@ func (s Service) ListHostSets(ctx context.Context, req *pbs.ListHostSetsRequest) } for _, item := range hl { res.Id = item.GetPublicId() - authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.WithResource(&res)).Strings() + idActions := idActionsTypeMap[host.SubtypeFromId(res.Id)] + authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), idActions, auth.WithResource(&res)).Strings() if len(authorizedActions) == 0 { continue } @@ -114,6 +142,9 @@ func (s Service) ListHostSets(ctx context.Context, req *pbs.ListHostSetsRequest) if outputFields.Has(globals.AuthorizedActionsField) { outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions)) } + if plg != nil { + outputOpts = append(outputOpts, handlers.WithPlugin(plg)) + } item, err := toProto(ctx, item, nil, outputOpts...) if err != nil { @@ -138,7 +169,7 @@ func (s Service) GetHostSet(ctx context.Context, req *pbs.GetHostSetRequest) (*p if authResults.Error != nil { return nil, authResults.Error } - hs, hosts, err := s.getFromRepo(ctx, req.GetId()) + hs, hosts, plg, err := s.getFromRepo(ctx, req.GetId()) if err != nil { return nil, err } @@ -148,13 +179,17 @@ func (s Service) GetHostSet(ctx context.Context, req *pbs.GetHostSetRequest) (*p return nil, errors.New(ctx, errors.Internal, op, "no request context found") } - outputOpts := make([]handlers.Option, 0, 3) + outputOpts := make([]handlers.Option, 0, 4) outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields)) if outputFields.Has(globals.ScopeField) { outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) } if outputFields.Has(globals.AuthorizedActionsField) { - outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, hs.GetPublicId(), IdActions).Strings())) + idActions := idActionsTypeMap[host.SubtypeFromId(req.GetId())] + outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, hs.GetPublicId(), idActions).Strings())) + } + if plg != nil { + outputOpts = append(outputOpts, handlers.WithPlugin(plg)) } item, err := toProto(ctx, hs, hosts, outputOpts...) @@ -169,14 +204,14 @@ func (s Service) GetHostSet(ctx context.Context, req *pbs.GetHostSetRequest) (*p func (s Service) CreateHostSet(ctx context.Context, req *pbs.CreateHostSetRequest) (*pbs.CreateHostSetResponse, error) { const op = "host_sets.(Service).CreateHostSet" - if err := validateCreateRequest(req); err != nil { + if err := validateCreateRequest(ctx, req); err != nil { return nil, err } _, authResults := s.parentAndAuthResult(ctx, req.GetItem().GetHostCatalogId(), action.Create) if authResults.Error != nil { return nil, authResults.Error } - hs, err := s.createInRepo(ctx, authResults.Scope.GetId(), req.GetItem().GetHostCatalogId(), req.GetItem()) + hs, plg, err := s.createInRepo(ctx, authResults.Scope.GetId(), req.GetItem().GetHostCatalogId(), req.GetItem()) if err != nil { return nil, err } @@ -192,7 +227,11 @@ func (s Service) CreateHostSet(ctx context.Context, req *pbs.CreateHostSetReques outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) } if outputFields.Has(globals.AuthorizedActionsField) { - outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, hs.GetPublicId(), IdActions).Strings())) + idActions := idActionsTypeMap[host.SubtypeFromId(req.GetItem().GetHostCatalogId())] + outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, hs.GetPublicId(), idActions).Strings())) + } + if plg != nil { + outputOpts = append(outputOpts, handlers.WithPlugin(plg)) } item, err := toProto(ctx, hs, nil, outputOpts...) @@ -210,14 +249,14 @@ func (s Service) CreateHostSet(ctx context.Context, req *pbs.CreateHostSetReques func (s Service) UpdateHostSet(ctx context.Context, req *pbs.UpdateHostSetRequest) (*pbs.UpdateHostSetResponse, error) { const op = "host_sets.(Service).UpdateHostSet" - if err := validateUpdateRequest(req); err != nil { + if err := validateUpdateRequest(ctx, req); err != nil { return nil, err } cat, authResults := s.parentAndAuthResult(ctx, req.GetId(), action.Update) if authResults.Error != nil { return nil, authResults.Error } - hs, hosts, err := s.updateInRepo(ctx, authResults.Scope.GetId(), cat.GetPublicId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem()) + hs, hosts, plg, err := s.updateInRepo(ctx, authResults.Scope.GetId(), cat.GetPublicId(), req) if err != nil { return nil, err } @@ -229,11 +268,15 @@ func (s Service) UpdateHostSet(ctx context.Context, req *pbs.UpdateHostSetReques outputOpts := make([]handlers.Option, 0, 3) outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields)) + if plg != nil { + outputOpts = append(outputOpts, handlers.WithPlugin(plg)) + } if outputFields.Has(globals.ScopeField) { outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) } if outputFields.Has(globals.AuthorizedActionsField) { - outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, hs.GetPublicId(), IdActions).Strings())) + idActions := idActionsTypeMap[host.SubtypeFromId(req.GetId())] + outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, hs.GetPublicId(), idActions).Strings())) } item, err := toProto(ctx, hs, hosts, outputOpts...) @@ -287,7 +330,8 @@ func (s Service) AddHostSetHosts(ctx context.Context, req *pbs.AddHostSetHostsRe outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) } if outputFields.Has(globals.AuthorizedActionsField) { - outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, hs.GetPublicId(), IdActions).Strings())) + idActions := idActionsTypeMap[host.SubtypeFromId(req.GetId())] + outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, hs.GetPublicId(), idActions).Strings())) } item, err := toProto(ctx, hs, hosts, outputOpts...) @@ -324,7 +368,8 @@ func (s Service) SetHostSetHosts(ctx context.Context, req *pbs.SetHostSetHostsRe outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) } if outputFields.Has(globals.AuthorizedActionsField) { - outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, hs.GetPublicId(), IdActions).Strings())) + idActions := idActionsTypeMap[host.SubtypeFromId(req.GetId())] + outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, hs.GetPublicId(), idActions).Strings())) } item, err := toProto(ctx, hs, hosts, outputOpts...) @@ -362,7 +407,8 @@ func (s Service) RemoveHostSetHosts(ctx context.Context, req *pbs.RemoveHostSetH outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) } if outputFields.Has(globals.AuthorizedActionsField) { - outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, hs.GetPublicId(), IdActions).Strings())) + idActions := idActionsTypeMap[host.SubtypeFromId(req.GetId())] + outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, hs.GetPublicId(), idActions).Strings())) } item, err := toProto(ctx, hs, hosts, outputOpts...) @@ -373,63 +419,111 @@ func (s Service) RemoveHostSetHosts(ctx context.Context, req *pbs.RemoveHostSetH return &pbs.RemoveHostSetHostsResponse{Item: item}, nil } -func (s Service) getFromRepo(ctx context.Context, id string) (*static.HostSet, []*static.Host, error) { - repo, err := s.staticRepoFn() - if err != nil { - return nil, nil, err +func (s Service) getFromRepo(ctx context.Context, id string) (host.Set, []host.Host, *plugins.PluginInfo, error) { + var hs host.Set + var hl []host.Host + var plg *plugins.PluginInfo + switch host.SubtypeFromId(id) { + case static.Subtype: + repo, err := s.staticRepoFn() + if err != nil { + return nil, nil, nil, err + } + hset, hosts, err := repo.LookupSet(ctx, id) + if err != nil { + return nil, nil, nil, err + } + if hset == nil { + return nil, nil, nil, handlers.NotFoundErrorf("Host Set %q doesn't exist.", id) + } + for _, h := range hosts { + hl = append(hl, h) + } + hs = hset + case plugin.Subtype: + repo, err := s.pluginRepoFn() + if err != nil { + return nil, nil, nil, err + } + hset, hsplg, err := repo.LookupSet(ctx, id) + if err != nil { + return nil, nil, nil, err + } + if hset == nil { + return nil, nil, nil, handlers.NotFoundErrorf("Host Set %q doesn't exist.", id) + } + hs = hset + plg = toPluginInfo(hsplg) + for _, h := range hset.HostIds { + hl = append(hl, &plugin.Host{ + Host: &plugstore.Host{ + PublicId: h, + CatalogId: hset.CatalogId, + }, + }) + } } - h, m, err := repo.LookupSet(ctx, id) - if err != nil { - return nil, nil, err - } - if h == nil { - return nil, nil, handlers.NotFoundErrorf("Host Set %q doesn't exist.", id) - } - return h, m, nil + return hs, hl, plg, nil } -func (s Service) createInRepo(ctx context.Context, scopeId, catalogId string, item *pb.HostSet) (*static.HostSet, error) { +func (s Service) createInRepo(ctx context.Context, scopeId, catalogId string, item *pb.HostSet) (host.Set, *plugins.PluginInfo, error) { const op = "host_sets.(Service).createInRepo" - var opts []static.Option - if item.GetName() != nil { - opts = append(opts, static.WithName(item.GetName().GetValue())) + if item == nil { + return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing item") } - if item.GetDescription() != nil { - opts = append(opts, static.WithDescription(item.GetDescription().GetValue())) + var hSet host.Set + var plg *plugins.PluginInfo + switch host.SubtypeFromId(catalogId) { + case static.Subtype: + h, err := toStorageStaticSet(ctx, catalogId, item) + if err != nil { + return nil, nil, err + } + repo, err := s.staticRepoFn() + if err != nil { + return nil, nil, err + } + out, err := repo.CreateSet(ctx, scopeId, h) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create host set")) + } + if out == nil { + return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create host set but no error returned from repository.") + } + hSet = out + case plugin.Subtype: + h, err := toStoragePluginSet(ctx, catalogId, item) + if err != nil { + return nil, nil, err + } + repo, err := s.pluginRepoFn() + if err != nil { + return nil, nil, err + } + out, hsplg, err := repo.CreateSet(ctx, scopeId, h) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create host set")) + } + if out == nil { + return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create host set but no error returned from repository.") + } + hSet = out + plg = toPluginInfo(hsplg) + default: + return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "unrecognized catalog type") } - h, err := static.NewHostSet(catalogId, opts...) - if err != nil { - return nil, errors.Wrap(ctx, err, op, errors.WithMsg("Unable to build host set for creation")) - } - repo, err := s.staticRepoFn() - if err != nil { - return nil, err - } - out, err := repo.CreateSet(ctx, scopeId, h) - if err != nil { - return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create host set")) - } - if out == nil { - return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create host set but no error returned from repository.") - } - return out, nil + return hSet, plg, nil } -func (s Service) updateInRepo(ctx context.Context, scopeId, catalogId, id string, mask []string, item *pb.HostSet) (*static.HostSet, []*static.Host, error) { - const op = "host_sets.(Service).updateInRepo" - var opts []static.Option - if desc := item.GetDescription(); desc != nil { - opts = append(opts, static.WithDescription(desc.GetValue())) - } - if name := item.GetName(); name != nil { - opts = append(opts, static.WithName(name.GetValue())) - } - h, err := static.NewHostSet(catalogId, opts...) +func (s Service) updateStaticInRepo(ctx context.Context, scopeId, catalogId string, req *pbs.UpdateHostSetRequest) (host.Set, []host.Host, error) { + const op = "host_sets.(Service).updateStaticInRepo" + item := req.GetItem() + h, err := toStorageStaticSet(ctx, catalogId, item) if err != nil { return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("Unable to build host set for update")) } - h.PublicId = id - dbMask := maskManager.Translate(mask) + h.PublicId = req.GetId() + dbMask := maskManager[static.Subtype].Translate(req.GetUpdateMask().GetPaths()) if len(dbMask) == 0 { return nil, nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."}) } @@ -442,37 +536,117 @@ func (s Service) updateInRepo(ctx context.Context, scopeId, catalogId, id string return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to update host set")) } if rowsUpdated == 0 { - return nil, nil, handlers.NotFoundErrorf("Host Set %q doesn't exist or incorrect version provided.", id) + return nil, nil, handlers.NotFoundErrorf("Host Set %q doesn't exist or incorrect version provided.", req.GetId()) } - return out, m, nil + var hl []host.Host + for _, h := range m { + hl = append(hl, h) + } + return out, hl, nil +} + +func (s Service) updatePluginInRepo(ctx context.Context, scopeId string, req *pbs.UpdateHostSetRequest) (host.Set, []host.Host, *plugins.PluginInfo, error) { + const op = "host_sets.(Service).updatePluginInRepo" + item := req.GetItem() + h, err := toStoragePluginSet(ctx, "", item) + if err != nil { + return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("Unable to build host set for update")) + } + h.PublicId = req.GetId() + dbMask := maskManager[plugin.Subtype].Translate(req.GetUpdateMask().GetPaths(), "attributes.") + if len(dbMask) == 0 { + return nil, nil, nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."}) + } + repo, err := s.pluginRepoFn() + if err != nil { + return nil, nil, nil, err + } + out, hosts, plg, rowsUpdated, err := repo.UpdateSet(ctx, scopeId, h, item.GetVersion(), dbMask) + if err != nil { + return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to update host set")) + } + if rowsUpdated == 0 { + return nil, nil, nil, handlers.NotFoundErrorf("Host Set %q doesn't exist or incorrect version provided.", req.GetId()) + } + var hl []host.Host + for _, h := range hosts { + hl = append(hl, h) + } + return out, hl, toPluginInfo(plg), nil +} + +func (s Service) updateInRepo(ctx context.Context, scopeId, catalogId string, req *pbs.UpdateHostSetRequest) (hs host.Set, hosts []host.Host, plg *plugins.PluginInfo, err error) { + const op = "host_sets.(Service).updateInRepo" + switch host.SubtypeFromId(req.GetId()) { + case static.Subtype: + hs, hosts, err = s.updateStaticInRepo(ctx, scopeId, catalogId, req) + case plugin.Subtype: + hs, hosts, plg, err = s.updatePluginInRepo(ctx, scopeId, req) + } + return } func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool, error) { const op = "host_sets.(Service).deleteFromRepo" - repo, err := s.staticRepoFn() - if err != nil { - return false, err - } - rows, err := repo.DeleteSet(ctx, scopeId, id) - if err != nil { - return false, errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete host")) + rows := 0 + switch host.SubtypeFromId(id) { + case static.Subtype: + repo, err := s.staticRepoFn() + if err != nil { + return false, err + } + rows, err = repo.DeleteSet(ctx, scopeId, id) + if err != nil { + return false, errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete host")) + } + case plugin.Subtype: + repo, err := s.pluginRepoFn() + if err != nil { + return false, err + } + rows, err = repo.DeleteSet(ctx, scopeId, id) + if err != nil { + return false, errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete host")) + } } return rows > 0, nil } -func (s Service) listFromRepo(ctx context.Context, catalogId string) ([]*static.HostSet, error) { - repo, err := s.staticRepoFn() - if err != nil { - return nil, err +func (s Service) listFromRepo(ctx context.Context, catalogId string, opt ...host.Option) ([]host.Set, *plugins.PluginInfo, error) { + const op = "host_sets.(Service).listFromRepo" + var plg *plugins.PluginInfo + var sets []host.Set + switch host.SubtypeFromId(catalogId) { + case static.Subtype: + repo, err := s.staticRepoFn() + if err != nil { + return nil, nil, err + } + sl, err := repo.ListSets(ctx, catalogId) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + for _, a := range sl { + sets = append(sets, a) + } + case plugin.Subtype: + repo, err := s.pluginRepoFn() + if err != nil { + return nil, nil, err + } + sl, hsplg, err := repo.ListSets(ctx, catalogId, opt...) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + for _, a := range sl { + sets = append(sets, a) + } + plg = toPluginInfo(hsplg) } - hl, err := repo.ListSets(ctx, catalogId) - if err != nil { - return nil, err - } - return hl, nil + return sets, plg, nil } -func (s Service) addInRepo(ctx context.Context, scopeId, setId string, hostIds []string, version uint32) (*static.HostSet, []*static.Host, error) { +func (s Service) addInRepo(ctx context.Context, scopeId, setId string, hostIds []string, version uint32) (*static.HostSet, []host.Host, error) { const op = "host_sets.(Service).addInRepo" repo, err := s.staticRepoFn() if err != nil { @@ -489,10 +663,14 @@ func (s Service) addInRepo(ctx context.Context, scopeId, setId string, hostIds [ if out == nil { return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup host set after adding hosts to it.") } - return out, m, nil + var hl []host.Host + for _, h := range m { + hl = append(hl, h) + } + return out, hl, nil } -func (s Service) setInRepo(ctx context.Context, scopeId, setId string, hostIds []string, version uint32) (*static.HostSet, []*static.Host, error) { +func (s Service) setInRepo(ctx context.Context, scopeId, setId string, hostIds []string, version uint32) (*static.HostSet, []host.Host, error) { const op = "host_sets.(Service).setInRepo" repo, err := s.staticRepoFn() if err != nil { @@ -510,10 +688,14 @@ func (s Service) setInRepo(ctx context.Context, scopeId, setId string, hostIds [ if out == nil { return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup host set after setting hosts for it.") } - return out, m, nil + var hl []host.Host + for _, h := range m { + hl = append(hl, h) + } + return out, hl, nil } -func (s Service) removeInRepo(ctx context.Context, scopeId, setId string, hostIds []string, version uint32) (*static.HostSet, []*static.Host, error) { +func (s Service) removeInRepo(ctx context.Context, scopeId, setId string, hostIds []string, version uint32) (*static.HostSet, []host.Host, error) { const op = "host_sets.(Service).removeInRepo" repo, err := s.staticRepoFn() if err != nil { @@ -530,12 +712,22 @@ func (s Service) removeInRepo(ctx context.Context, scopeId, setId string, hostId if out == nil { return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup host set after removing hosts from it.") } - return out, m, nil + var hl []host.Host + for _, h := range m { + hl = append(hl, h) + } + return out, hl, nil } -func (s Service) parentAndAuthResult(ctx context.Context, id string, a action.Type) (*static.HostCatalog, auth.VerifyResults) { +func (s Service) parentAndAuthResult(ctx context.Context, id string, a action.Type) (host.Catalog, auth.VerifyResults) { res := auth.VerifyResults{} - repo, err := s.staticRepoFn() + + staticRepo, err := s.staticRepoFn() + if err != nil { + res.Error = err + return nil, res + } + pluginRepo, err := s.pluginRepoFn() if err != nil { res.Error = err return nil, res @@ -547,33 +739,77 @@ func (s Service) parentAndAuthResult(ctx context.Context, id string, a action.Ty case action.List, action.Create: parentId = id default: - set, _, err := repo.LookupSet(ctx, id) - if err != nil { - res.Error = err - return nil, res - } - if set == nil { - res.Error = handlers.NotFoundError() - return nil, res + var set host.Set + switch host.SubtypeFromId(id) { + case static.Subtype: + ss, _, err := staticRepo.LookupSet(ctx, id) + if err != nil { + res.Error = err + return nil, res + } + if ss == nil { + res.Error = handlers.NotFoundError() + return nil, res + } + set = ss + case plugin.Subtype: + ps, _, err := pluginRepo.LookupSet(ctx, id) + if err != nil { + res.Error = err + return nil, res + } + if ps == nil { + res.Error = handlers.NotFoundError() + return nil, res + } + set = ps } parentId = set.GetCatalogId() opts = append(opts, auth.WithId(id)) } - cat, err := repo.LookupCatalog(ctx, parentId) - if err != nil { - res.Error = err - return nil, res - } - if cat == nil { - res.Error = handlers.NotFoundError() - return nil, res + var cat host.Catalog + switch host.SubtypeFromId(id) { + case static.Subtype: + cs, err := staticRepo.LookupCatalog(ctx, parentId) + if err != nil { + res.Error = err + return nil, res + } + if cs == nil { + res.Error = handlers.NotFoundError() + return nil, res + } + cat = cs + case plugin.Subtype: + pc, _, err := pluginRepo.LookupCatalog(ctx, parentId) + if err != nil { + res.Error = err + return nil, res + } + if pc == nil { + res.Error = handlers.NotFoundError() + return nil, res + } + cat = pc } opts = append(opts, auth.WithScopeId(cat.GetScopeId()), auth.WithPin(parentId)) return cat, auth.Verify(ctx, opts...) } -func toProto(ctx context.Context, in *static.HostSet, hosts []*static.Host, opt ...handlers.Option) (*pb.HostSet, error) { +func toPluginInfo(plg *hostplugin.Plugin) *plugins.PluginInfo { + if plg == nil { + return nil + } + return &plugins.PluginInfo{ + Id: plg.GetPublicId(), + Name: plg.GetName(), + Description: plg.GetDescription(), + } +} + +func toProto(ctx context.Context, in host.Set, hosts []host.Host, opt ...handlers.Option) (*pb.HostSet, error) { + const op = "host_set_service.toProto" opts := handlers.GetOpts(opt...) if opts.WithOutputFields == nil { return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building hostset proto") @@ -588,7 +824,12 @@ func toProto(ctx context.Context, in *static.HostSet, hosts []*static.Host, opt out.HostCatalogId = in.GetCatalogId() } if outputFields.Has(globals.TypeField) { - out.Type = static.Subtype.String() + switch in.(type) { + case *static.HostSet: + out.Type = static.Subtype.String() + case *plugin.HostSet: + out.Type = plugin.Subtype.String() + } } if outputFields.Has(globals.DescriptionField) && in.GetDescription() != "" { out.Description = &wrapperspb.StringValue{Value: in.GetDescription()} @@ -608,6 +849,9 @@ func toProto(ctx context.Context, in *static.HostSet, hosts []*static.Host, opt if outputFields.Has(globals.ScopeField) { out.Scope = opts.WithScope } + if outputFields.Has(globals.PluginField) { + out.Plugin = opts.WithPlugin + } if outputFields.Has(globals.AuthorizedActionsField) { out.AuthorizedActions = opts.WithAuthorizedActions } @@ -616,9 +860,71 @@ func toProto(ctx context.Context, in *static.HostSet, hosts []*static.Host, opt out.HostIds = append(out.HostIds, h.GetPublicId()) } } + + switch h := in.(type) { + case *plugin.HostSet: + if outputFields.Has(globals.PreferredEndpointsField) { + out.PreferredEndpoints = h.PreferredEndpoints + } + if outputFields.Has(globals.SyncIntervalSecondsField) && h.GetSyncIntervalSeconds() != 0 { + out.SyncIntervalSeconds = &wrapperspb.Int32Value{Value: h.GetSyncIntervalSeconds()} + } + if outputFields.Has(globals.AttributesField) { + attrs := &structpb.Struct{} + err := proto.Unmarshal(h.Attributes, attrs) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + if len(attrs.GetFields()) > 0 { + out.Attributes = attrs + } + } + } + return &out, nil } +func toStorageStaticSet(ctx context.Context, catalogId string, item *pb.HostSet) (*static.HostSet, error) { + const op = "host_set_service.toStorageStaticSet" + var opts []static.Option + if item.GetName() != nil { + opts = append(opts, static.WithName(item.GetName().GetValue())) + } + if item.GetDescription() != nil { + opts = append(opts, static.WithDescription(item.GetDescription().GetValue())) + } + hs, err := static.NewHostSet(catalogId, opts...) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("Unable to build host set for creation")) + } + return hs, nil +} + +func toStoragePluginSet(ctx context.Context, catalogId string, item *pb.HostSet) (*plugin.HostSet, error) { + const op = "host_set_service.toStoragePluginSet" + var opts []plugin.Option + if item.GetName() != nil { + opts = append(opts, plugin.WithName(item.GetName().GetValue())) + } + if item.GetDescription() != nil { + opts = append(opts, plugin.WithDescription(item.GetDescription().GetValue())) + } + if item.GetAttributes() != nil { + opts = append(opts, plugin.WithAttributes(item.GetAttributes())) + } + if item.GetPreferredEndpoints() != nil { + opts = append(opts, plugin.WithPreferredEndpoints(item.GetPreferredEndpoints())) + } + if item.GetSyncIntervalSeconds() != nil { + opts = append(opts, plugin.WithSyncIntervalSeconds(item.GetSyncIntervalSeconds().GetValue())) + } + hs, err := plugin.NewHostSet(ctx, catalogId, opts...) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("Unable to build host set for creation")) + } + return hs, nil +} + // A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce // requirements on the structure of the request. They verify that: // * The path passed in is correctly formatted @@ -627,49 +933,79 @@ func toProto(ctx context.Context, in *static.HostSet, hosts []*static.Host, opt // * The type asserted by the ID and/or field is known // * If relevant, the type derived from the id prefix matches what is claimed by the type field func validateGetRequest(req *pbs.GetHostSetRequest) error { - return handlers.ValidateGetRequest(handlers.NoopValidatorFn, req, static.HostSetPrefix) + return handlers.ValidateGetRequest(handlers.NoopValidatorFn, req, static.HostSetPrefix, plugin.HostSetPrefix) } -func validateCreateRequest(req *pbs.CreateHostSetRequest) error { +func validateCreateRequest(ctx context.Context, req *pbs.CreateHostSetRequest) error { return handlers.ValidateCreateRequest(req.GetItem(), func() map[string]string { badFields := map[string]string{} - if !handlers.ValidId(handlers.Id(req.GetItem().GetHostCatalogId()), static.HostCatalogPrefix) { - badFields["host_catalog_id"] = "The field is incorrectly formatted." + if !handlers.ValidId(handlers.Id(req.GetItem().GetHostCatalogId()), static.HostCatalogPrefix, plugin.HostCatalogPrefix) { + badFields[globals.HostCatalogIdField] = "The field is incorrectly formatted." + } + if len(req.GetItem().GetPreferredEndpoints()) > 0 { + _, err := endpoint.NewPreferencer(ctx, endpoint.WithPreferenceOrder(req.GetItem().GetPreferredEndpoints())) + if err != nil { + badFields[globals.PreferredEndpointsField] = fmt.Errorf("Error parsing preferred endpoints: %w.", err).Error() + } } switch host.SubtypeFromId(req.GetItem().GetHostCatalogId()) { case static.Subtype: if req.GetItem().GetType() != "" && req.GetItem().GetType() != static.Subtype.String() { - badFields["type"] = "Doesn't match the parent resource's type." + badFields[globals.TypeField] = "Doesn't match the parent resource's type." + } + if len(req.GetItem().PreferredEndpoints) > 0 { + badFields[globals.PreferredEndpointsField] = "This field is not yet supported for static host sets." + } + case plugin.Subtype: + if req.GetItem().GetType() != "" && req.GetItem().GetType() != plugin.Subtype.String() { + badFields[globals.TypeField] = "Doesn't match the parent resource's type." + } + if val := req.GetItem().GetSyncIntervalSeconds(); val != nil { + if val.GetValue() == 0 || val.GetValue() < -1 { + badFields[globals.SyncIntervalSecondsField] = "Must be -1 or a positive integer." + } } } return badFields }) } -func validateUpdateRequest(req *pbs.UpdateHostSetRequest) error { +func validateUpdateRequest(ctx context.Context, req *pbs.UpdateHostSetRequest) error { return handlers.ValidateUpdateRequest(req, req.GetItem(), func() map[string]string { badFields := map[string]string{} + if len(req.GetItem().GetPreferredEndpoints()) > 0 { + _, err := endpoint.NewPreferencer(ctx, endpoint.WithPreferenceOrder(req.GetItem().GetPreferredEndpoints())) + if err != nil { + badFields[globals.PreferredEndpointsField] = fmt.Errorf("Error parsing preferred endpoints: %w.", err).Error() + } + } switch host.SubtypeFromId(req.GetId()) { case static.Subtype: if req.GetItem().GetType() != "" && req.GetItem().GetType() != static.Subtype.String() { - badFields["type"] = "Cannot modify the resource type." + badFields[globals.TypeField] = "Cannot modify the resource type." + } + case plugin.Subtype: + if val := req.GetItem().GetSyncIntervalSeconds(); val != nil { + if val.GetValue() == 0 || val.GetValue() < -1 { + badFields[globals.SyncIntervalSecondsField] = "Must be -1 or a positive integer." + } } } return badFields - }, static.HostSetPrefix) + }, static.HostSetPrefix, plugin.HostSetPrefix) } func validateDeleteRequest(req *pbs.DeleteHostSetRequest) error { - return handlers.ValidateDeleteRequest(handlers.NoopValidatorFn, req, static.HostSetPrefix) + return handlers.ValidateDeleteRequest(handlers.NoopValidatorFn, req, static.HostSetPrefix, plugin.HostSetPrefix) } func validateListRequest(req *pbs.ListHostSetsRequest) error { badFields := map[string]string{} - if !handlers.ValidId(handlers.Id(req.GetHostCatalogId()), static.HostCatalogPrefix) { - badFields["host_catalog_id"] = "The field is incorrectly formatted." + if !handlers.ValidId(handlers.Id(req.GetHostCatalogId()), static.HostCatalogPrefix, plugin.HostCatalogPrefix) { + badFields[globals.HostCatalogIdField] = "The field is incorrectly formatted." } if _, err := handlers.NewFilter(req.GetFilter()); err != nil { - badFields["filter"] = fmt.Sprintf("This field could not be parsed. %v", err) + badFields[globals.FilterField] = fmt.Sprintf("This field could not be parsed. %v", err) } if len(badFields) > 0 { return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields) @@ -680,17 +1016,17 @@ func validateListRequest(req *pbs.ListHostSetsRequest) error { func validateAddRequest(req *pbs.AddHostSetHostsRequest) error { badFields := map[string]string{} if !handlers.ValidId(handlers.Id(req.GetId()), static.HostSetPrefix) { - badFields["id"] = "Incorrectly formatted identifier." + badFields[globals.IdField] = "Incorrectly formatted identifier." } if req.GetVersion() == 0 { - badFields["version"] = "Required field." + badFields[globals.VersionField] = "Required field." } if len(req.GetHostIds()) == 0 { - badFields["host_ids"] = "Must be non-empty." + badFields[globals.HostIdsField] = "Must be non-empty." } for _, id := range req.GetHostIds() { if !handlers.ValidId(handlers.Id(id), static.HostPrefix) { - badFields["host_ids"] = "Incorrectly formatted host identifier." + badFields[globals.HostIdsField] = "Incorrectly formatted host identifier." break } } @@ -703,14 +1039,14 @@ func validateAddRequest(req *pbs.AddHostSetHostsRequest) error { func validateSetRequest(req *pbs.SetHostSetHostsRequest) error { badFields := map[string]string{} if !handlers.ValidId(handlers.Id(req.GetId()), static.HostSetPrefix) { - badFields["id"] = "Incorrectly formatted identifier." + badFields[globals.IdField] = "Incorrectly formatted identifier." } if req.GetVersion() == 0 { - badFields["version"] = "Required field." + badFields[globals.VersionField] = "Required field." } for _, id := range req.GetHostIds() { if !handlers.ValidId(handlers.Id(id), static.HostPrefix) { - badFields["host_ids"] = "Incorrectly formatted host identifier." + badFields[globals.HostIdsField] = "Incorrectly formatted host identifier." break } } @@ -723,17 +1059,17 @@ func validateSetRequest(req *pbs.SetHostSetHostsRequest) error { func validateRemoveRequest(req *pbs.RemoveHostSetHostsRequest) error { badFields := map[string]string{} if !handlers.ValidId(handlers.Id(req.GetId()), static.HostSetPrefix) { - badFields["id"] = "Incorrectly formatted identifier." + badFields[globals.IdField] = "Incorrectly formatted identifier." } if req.GetVersion() == 0 { - badFields["version"] = "Required field." + badFields[globals.VersionField] = "Required field." } if len(req.GetHostIds()) == 0 { - badFields["host_ids"] = "Must be non-empty." + badFields[globals.HostIdsField] = "Must be non-empty." } for _, id := range req.GetHostIds() { if !handlers.ValidId(handlers.Id(id), static.HostPrefix) { - badFields["host_ids"] = "Incorrectly formatted host identifier." + badFields[globals.HostIdsField] = "Incorrectly formatted host identifier." break } } diff --git a/internal/servers/controller/handlers/host_sets/host_set_service_test.go b/internal/servers/controller/handlers/host_sets/host_set_service_test.go index c08efc732..0f8b0c82a 100644 --- a/internal/servers/controller/handlers/host_sets/host_set_service_test.go +++ b/internal/servers/controller/handlers/host_sets/host_set_service_test.go @@ -12,15 +12,22 @@ import ( "github.com/google/go-cmp/cmp" "github.com/hashicorp/boundary/internal/db" pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" + "github.com/hashicorp/boundary/internal/host" + "github.com/hashicorp/boundary/internal/host/plugin" "github.com/hashicorp/boundary/internal/host/static" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" + hostplugin "github.com/hashicorp/boundary/internal/plugin/host" + "github.com/hashicorp/boundary/internal/scheduler" "github.com/hashicorp/boundary/internal/servers/controller/auth" "github.com/hashicorp/boundary/internal/servers/controller/handlers" "github.com/hashicorp/boundary/internal/servers/controller/handlers/host_sets" "github.com/hashicorp/boundary/internal/types/scope" + "github.com/hashicorp/boundary/internal/types/subtypes" pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets" + "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/plugins" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/genproto/protobuf/field_mask" @@ -28,16 +35,22 @@ import ( "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" ) -var testAuthorizedActions = []string{"no-op", "read", "update", "delete", "add-hosts", "set-hosts", "remove-hosts"} +var testAuthorizedActions = map[subtypes.Subtype][]string{ + static.Subtype: {"no-op", "read", "update", "delete", "add-hosts", "set-hosts", "remove-hosts"}, + plugin.Subtype: {"no-op", "read", "update", "delete"}, +} -func TestGet(t *testing.T) { +func TestGet_Static(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { @@ -50,6 +63,9 @@ func TestGet(t *testing.T) { repoFn := func() (*static.Repository, error) { return static.NewRepository(rw, rw, kms) } + pluginRepoFn := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) + } hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] @@ -67,7 +83,7 @@ func TestGet(t *testing.T) { Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org.GetPublicId()}, Type: "static", HostIds: hIds, - AuthorizedActions: testAuthorizedActions, + AuthorizedActions: testAuthorizedActions[static.Subtype], } cases := []struct { @@ -106,7 +122,7 @@ func TestGet(t *testing.T) { req := proto.Clone(toMerge).(*pbs.GetHostSetRequest) proto.Merge(req, tc.req) - s, err := host_sets.NewService(repoFn) + s, err := host_sets.NewService(repoFn, pluginRepoFn) require.NoError(err, "Couldn't create a new host set service.") got, gErr := s.GetHostSet(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), req) @@ -123,10 +139,12 @@ func TestGet(t *testing.T) { } } -func TestList(t *testing.T) { +func TestGet_Plugin(t *testing.T) { + t.Parallel() conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { @@ -139,6 +157,114 @@ func TestList(t *testing.T) { repoFn := func() (*static.Repository, error) { return static.NewRepository(rw, rw, kms) } + + name := "test" + prefEndpoints := []string{"cidr:1.2.3.4", "cidr:2.3.4.5/24"} + plg := hostplugin.TestPlugin(t, conn, name) + plgm := map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): plugin.NewWrappingPluginClient(&plugin.TestPluginServer{}), + } + pluginRepoFn := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, plgm) + } + + hc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) + hs := plugin.TestSet(t, conn, kms, sche, hc, plgm, plugin.WithPreferredEndpoints(prefEndpoints), plugin.WithSyncIntervalSeconds(-1)) + + toMerge := &pbs.GetHostSetRequest{} + + pHost := &pb.HostSet{ + HostCatalogId: hc.GetPublicId(), + Id: hs.GetPublicId(), + CreatedTime: hs.CreateTime.GetTimestamp(), + UpdatedTime: hs.UpdateTime.GetTimestamp(), + Type: plugin.Subtype.String(), + Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org.GetPublicId()}, + Plugin: &plugins.PluginInfo{ + Id: plg.GetPublicId(), + Name: plg.GetName(), + Description: plg.GetDescription(), + }, + PreferredEndpoints: prefEndpoints, + SyncIntervalSeconds: &wrappers.Int32Value{Value: -1}, + AuthorizedActions: testAuthorizedActions[plugin.Subtype], + } + + cases := []struct { + name string + req *pbs.GetHostSetRequest + res *pbs.GetHostSetResponse + err error + }{ + { + name: "Get an Existing Host", + req: &pbs.GetHostSetRequest{Id: hs.GetPublicId()}, + res: &pbs.GetHostSetResponse{Item: pHost}, + }, + { + name: "Get a non existing Host Set", + req: &pbs.GetHostSetRequest{Id: plugin.HostSetPrefix + "_DoesntExis"}, + res: nil, + err: handlers.ApiErrorWithCode(codes.NotFound), + }, + { + name: "Wrong id prefix", + req: &pbs.GetHostSetRequest{Id: "j_1234567890"}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "space in id", + req: &pbs.GetHostSetRequest{Id: plugin.HostSetPrefix + "_1 23456789"}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + req := proto.Clone(toMerge).(*pbs.GetHostSetRequest) + proto.Merge(req, tc.req) + + s, err := host_sets.NewService(repoFn, pluginRepoFn) + require.NoError(err, "Couldn't create a new host set service.") + + got, gErr := s.GetHostSet(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), req) + if tc.err != nil { + require.Error(gErr) + assert.True(errors.Is(gErr, tc.err), "GetHostSet(%+v) got error %v, wanted %v", req, gErr, tc.err) + return + } + require.NoError(gErr) + + if tc.res != nil { + tc.res.Item.Version = 1 + } + assert.Empty(cmp.Diff(got.GetItem(), tc.res.GetItem(), protocmp.Transform()), "GetHostSet(%q) got response %q, wanted %q", req, got, tc.res) + }) + } +} + +func TestList_Static(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + + iamRepo := iam.TestRepo(t, conn, wrapper) + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + + org, proj := iam.TestScopes(t, iamRepo) + + rw := db.New(conn) + repoFn := func() (*static.Repository, error) { + return static.NewRepository(rw, rw, kms) + } + pluginRepoFn := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) + } hcs := static.TestCatalogs(t, conn, proj.GetPublicId(), 2) hc, hcNoHosts := hcs[0], hcs[1] @@ -152,7 +278,7 @@ func TestList(t *testing.T) { UpdatedTime: h.GetUpdateTime().GetTimestamp(), Version: h.GetVersion(), Type: static.Subtype.String(), - AuthorizedActions: testAuthorizedActions, + AuthorizedActions: testAuthorizedActions[static.Subtype], }) } @@ -191,7 +317,7 @@ func TestList(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - s, err := host_sets.NewService(repoFn) + s, err := host_sets.NewService(repoFn, pluginRepoFn) require.NoError(err, "Couldn't create new host set service.") // Test with non-anon user @@ -218,11 +344,125 @@ func TestList(t *testing.T) { } } -func TestDelete(t *testing.T) { +func TestList_Plugin(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + + iamRepo := iam.TestRepo(t, conn, wrapper) + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + + org, proj := iam.TestScopes(t, iamRepo) + + rw := db.New(conn) + repoFn := func() (*static.Repository, error) { + return static.NewRepository(rw, rw, kms) + } + name := "test" + plg := hostplugin.TestPlugin(t, conn, name) + plgm := map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): plugin.NewWrappingPluginClient(&plugin.TestPluginServer{}), + } + pluginRepoFn := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, plgm) + } + hc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) + hcNoHosts := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) + preferredEndpoints := []string{"cidr:1.2.3.4", "dns:*.foobar.com"} + + var wantHs []*pb.HostSet + for i := 0; i < 10; i++ { + h := plugin.TestSet(t, conn, kms, sche, hc, plgm, plugin.WithPreferredEndpoints(preferredEndpoints), plugin.WithSyncIntervalSeconds(5)) + wantHs = append(wantHs, &pb.HostSet{ + Id: h.GetPublicId(), + HostCatalogId: h.GetCatalogId(), + Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org.GetPublicId()}, + Plugin: &plugins.PluginInfo{ + Id: plg.GetPublicId(), + Name: plg.GetName(), + Description: plg.GetDescription(), + }, + CreatedTime: h.GetCreateTime().GetTimestamp(), + UpdatedTime: h.GetUpdateTime().GetTimestamp(), + Version: h.GetVersion(), + Type: plugin.Subtype.String(), + AuthorizedActions: testAuthorizedActions[plugin.Subtype], + PreferredEndpoints: preferredEndpoints, + SyncIntervalSeconds: &wrappers.Int32Value{Value: 5}, + }) + } + + cases := []struct { + name string + req *pbs.ListHostSetsRequest + res *pbs.ListHostSetsResponse + err error + }{ + { + name: "List Many Host Sets", + req: &pbs.ListHostSetsRequest{HostCatalogId: hc.GetPublicId()}, + res: &pbs.ListHostSetsResponse{Items: wantHs}, + }, + { + name: "List No Host Sets", + req: &pbs.ListHostSetsRequest{HostCatalogId: hcNoHosts.GetPublicId()}, + res: &pbs.ListHostSetsResponse{}, + }, + { + name: "Filter To One Host Set", + req: &pbs.ListHostSetsRequest{HostCatalogId: hc.GetPublicId(), Filter: fmt.Sprintf(`"/item/id"==%q`, wantHs[1].GetId())}, + res: &pbs.ListHostSetsResponse{Items: wantHs[1:2]}, + }, + { + name: "Filter To No Host Sets", + req: &pbs.ListHostSetsRequest{HostCatalogId: hc.GetPublicId(), Filter: `"/item/name"=="doesnt match"`}, + res: &pbs.ListHostSetsResponse{}, + }, + { + name: "Filter Bad Format", + req: &pbs.ListHostSetsRequest{HostCatalogId: hc.GetPublicId(), Filter: `"//id/"=="bad"`}, + err: handlers.InvalidArgumentErrorf("bad format", nil), + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + s, err := host_sets.NewService(repoFn, pluginRepoFn) + require.NoError(err, "Couldn't create new host set service.") + + // Test with non-anon user + got, gErr := s.ListHostSetsWithOptions(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), tc.req, host.WithOrderByCreateTime(false)) + if tc.err != nil { + require.Error(gErr) + assert.True(errors.Is(gErr, tc.err), "ListHostSets(%q) got error %v, wanted %v", tc.req, gErr, tc.err) + return + } + require.NoError(gErr) + assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListHostSets(%q) got response %q, wanted %q", tc.req, got, tc.res) + + // Test with anon user + got, gErr = s.ListHostSets(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId(), auth.WithUserId(auth.AnonymousUserId)), tc.req) + require.NoError(gErr) + assert.Len(got.Items, len(tc.res.Items)) + for _, item := range got.GetItems() { + require.Nil(item.CreatedTime) + require.Nil(item.UpdatedTime) + require.Zero(item.Version) + require.Empty(item.HostIds) + } + }) + } +} + +func TestDelete_Static(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { @@ -235,10 +475,13 @@ func TestDelete(t *testing.T) { repoFn := func() (*static.Repository, error) { return static.NewRepository(rw, rw, kms) } + pluginRepoFn := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) + } hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] h := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] - s, err := host_sets.NewService(repoFn) + s, err := host_sets.NewService(repoFn, pluginRepoFn) require.NoError(t, err, "Couldn't create a new host set service.") cases := []struct { @@ -264,18 +507,87 @@ func TestDelete(t *testing.T) { err: handlers.ApiErrorWithCode(codes.NotFound), }, { - name: "Delete bad host catalog id in Host Set", + name: "Bad Host Id formatting", + scopeId: proj.GetPublicId(), + req: &pbs.DeleteHostSetRequest{ + Id: static.HostSetPrefix + "_bad_format", + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + got, gErr := s.DeleteHostSet(auth.DisabledAuthTestContext(iamRepoFn, tc.scopeId), tc.req) + if tc.err != nil { + require.Error(gErr) + assert.True(errors.Is(gErr, tc.err), "DeleteHostSet(%+v) got error %v, wanted %v", tc.req, gErr, tc.err) + } + assert.Empty(cmp.Diff(tc.res, got, protocmp.Transform()), "DeleteHostSet(%q) got response %q, wanted %q", tc.req, got, tc.res) + }) + } +} + +func TestDelete_Plugin(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + + iamRepo := iam.TestRepo(t, conn, wrapper) + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + + _, proj := iam.TestScopes(t, iamRepo) + + rw := db.New(conn) + repoFn := func() (*static.Repository, error) { + return static.NewRepository(rw, rw, kms) + } + pluginRepoFn := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) + } + name := "test" + plg := hostplugin.TestPlugin(t, conn, name) + plgm := map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): plugin.NewWrappingPluginClient(&plugin.TestPluginServer{}), + } + + hc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) + h := plugin.TestSet(t, conn, kms, sche, hc, plgm) + + s, err := host_sets.NewService(repoFn, pluginRepoFn) + require.NoError(t, err, "Couldn't create a new host set service.") + + cases := []struct { + name string + scopeId string + req *pbs.DeleteHostSetRequest + res *pbs.DeleteHostSetResponse + err error + }{ + { + name: "Delete an Existing Host Set", scopeId: proj.GetPublicId(), req: &pbs.DeleteHostSetRequest{ Id: h.GetPublicId(), }, + }, + { + name: "Delete bad id Host Set", + scopeId: proj.GetPublicId(), + req: &pbs.DeleteHostSetRequest{ + Id: plugin.HostSetPrefix + "_doesntexis", + }, err: handlers.ApiErrorWithCode(codes.NotFound), }, { name: "Bad Host Id formatting", scopeId: proj.GetPublicId(), req: &pbs.DeleteHostSetRequest{ - Id: static.HostSetPrefix + "_bad_format", + Id: plugin.HostSetPrefix + "_bad_format", }, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, @@ -299,6 +611,7 @@ func TestDelete_twice(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { @@ -311,10 +624,13 @@ func TestDelete_twice(t *testing.T) { repoFn := func() (*static.Repository, error) { return static.NewRepository(rw, rw, kms) } + plgRepoFn := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) + } hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] h := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] - s, err := host_sets.NewService(repoFn) + s, err := host_sets.NewService(repoFn, plgRepoFn) require.NoError(err, "Couldn't create a new host set service.") req := &pbs.DeleteHostSetRequest{ Id: h.GetPublicId(), @@ -327,11 +643,12 @@ func TestDelete_twice(t *testing.T) { assert.True(errors.Is(gErr, handlers.ApiErrorWithCode(codes.NotFound)), "Expected permission denied for the second delete.") } -func TestCreate(t *testing.T) { +func TestCreate_Static(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { @@ -344,7 +661,11 @@ func TestCreate(t *testing.T) { repoFn := func() (*static.Repository, error) { return static.NewRepository(rw, rw, kms) } + plgRepoFn := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) + } hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] + prefEndpoints := []string{"cidr:1.2.3.4", "cidr:2.3.4.5/24"} defaultHcCreated := hc.GetCreateTime().GetTimestamp().AsTime() @@ -370,10 +691,21 @@ func TestCreate(t *testing.T) { Name: &wrappers.StringValue{Value: "name"}, Description: &wrappers.StringValue{Value: "desc"}, Type: "static", - AuthorizedActions: testAuthorizedActions, + AuthorizedActions: testAuthorizedActions[static.Subtype], }, }, }, + { + name: "With Preferred Endpoints", + req: &pbs.CreateHostSetRequest{Item: &pb.HostSet{ + HostCatalogId: hc.GetPublicId(), + Name: &wrappers.StringValue{Value: "name"}, + Description: &wrappers.StringValue{Value: "desc"}, + Type: static.Subtype.String(), + PreferredEndpoints: prefEndpoints, + }}, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, { name: "Create with unknown type", req: &pbs.CreateHostSetRequest{Item: &pb.HostSet{ @@ -399,7 +731,7 @@ func TestCreate(t *testing.T) { Name: &wrappers.StringValue{Value: "no type name"}, Description: &wrappers.StringValue{Value: "no type desc"}, Type: "static", - AuthorizedActions: testAuthorizedActions, + AuthorizedActions: testAuthorizedActions[static.Subtype], }, }, }, @@ -435,7 +767,7 @@ func TestCreate(t *testing.T) { t.Run(tc.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - s, err := host_sets.NewService(repoFn) + s, err := host_sets.NewService(repoFn, plgRepoFn) require.NoError(err, "Failed to create a new host set service.") got, gErr := s.CreateHostSet(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), tc.req) @@ -467,11 +799,275 @@ func TestCreate(t *testing.T) { } } -func TestUpdate(t *testing.T) { +func TestCreate_Plugin(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + + iamRepo := iam.TestRepo(t, conn, wrapper) + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + + org, proj := iam.TestScopes(t, iamRepo) + + rw := db.New(conn) + repoFn := func() (*static.Repository, error) { + return static.NewRepository(rw, rw, kms) + } + name := "test" + plg := hostplugin.TestPlugin(t, conn, name) + plgRepoFn := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): plugin.NewWrappingPluginClient(&plugin.TestPluginServer{ + OnCreateSetFn: func(ctx context.Context, req *plgpb.OnCreateSetRequest) (*plgpb.OnCreateSetResponse, error) { + return nil, nil + }, + }), + }) + } + hc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) + + attrs := map[string]interface{}{ + "int": 1, + "zero int": 0, + "string": "foo", + "zero string": "", + "bytes": []byte("bar"), + "zero bytes": nil, + "bool": true, + "zero bool": false, + "nested": map[string]interface{}{ + "int": 1, + "zero int": 0, + "string": "foo", + "zero string": "", + "bytes": []byte("bar"), + "zero bytes": nil, + "bool": true, + "zero bool": false, + }, + } + testInputAttrs, err := structpb.NewStruct(attrs) + require.NoError(t, err) + // The result should clear out all keys with nil values... + delete(attrs, "zero bytes") + delete(attrs["nested"].(map[string]interface{}), "zero bytes") + testOutputAttrs, err := structpb.NewStruct(attrs) + require.NoError(t, err) + + prefEndpoints := []string{"cidr:1.2.3.4", "cidr:2.3.4.5/24"} + + defaultHcCreated := hc.GetCreateTime().GetTimestamp().AsTime() + + cases := []struct { + name string + req *pbs.CreateHostSetRequest + res *pbs.CreateHostSetResponse + err error + }{ + { + name: "No Attributes", + req: &pbs.CreateHostSetRequest{Item: &pb.HostSet{ + HostCatalogId: hc.GetPublicId(), + Name: &wrappers.StringValue{Value: "No Attributes"}, + Description: &wrappers.StringValue{Value: "desc"}, + Type: plugin.Subtype.String(), + SyncIntervalSeconds: &wrapperspb.Int32Value{Value: -1}, + }}, + res: &pbs.CreateHostSetResponse{ + Uri: fmt.Sprintf("host-sets/%s_", plugin.HostSetPrefix), + Item: &pb.HostSet{ + HostCatalogId: hc.GetPublicId(), + Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org.GetPublicId()}, + Plugin: &plugins.PluginInfo{ + Id: plg.GetPublicId(), + Name: plg.GetName(), + Description: plg.GetDescription(), + }, + Name: &wrappers.StringValue{Value: "No Attributes"}, + Description: &wrappers.StringValue{Value: "desc"}, + Type: plugin.Subtype.String(), + AuthorizedActions: testAuthorizedActions[plugin.Subtype], + SyncIntervalSeconds: &wrapperspb.Int32Value{Value: -1}, + }, + }, + }, + { + name: "With Attributes", + req: &pbs.CreateHostSetRequest{Item: &pb.HostSet{ + HostCatalogId: hc.GetPublicId(), + Name: &wrappers.StringValue{Value: "With Attributes"}, + Description: &wrappers.StringValue{Value: "desc"}, + Type: plugin.Subtype.String(), + SyncIntervalSeconds: &wrapperspb.Int32Value{Value: 90}, + Attributes: testInputAttrs, + }}, + res: &pbs.CreateHostSetResponse{ + Uri: fmt.Sprintf("host-sets/%s_", plugin.HostSetPrefix), + Item: &pb.HostSet{ + HostCatalogId: hc.GetPublicId(), + Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org.GetPublicId()}, + Plugin: &plugins.PluginInfo{ + Id: plg.GetPublicId(), + Name: plg.GetName(), + Description: plg.GetDescription(), + }, + Name: &wrappers.StringValue{Value: "With Attributes"}, + Description: &wrappers.StringValue{Value: "desc"}, + Type: plugin.Subtype.String(), + SyncIntervalSeconds: &wrapperspb.Int32Value{Value: 90}, + AuthorizedActions: testAuthorizedActions[plugin.Subtype], + Attributes: testOutputAttrs, + }, + }, + }, + { + name: "With Preferred Endpoints", + req: &pbs.CreateHostSetRequest{Item: &pb.HostSet{ + HostCatalogId: hc.GetPublicId(), + Name: &wrappers.StringValue{Value: "name"}, + Description: &wrappers.StringValue{Value: "desc"}, + Type: plugin.Subtype.String(), + PreferredEndpoints: prefEndpoints, + }}, + res: &pbs.CreateHostSetResponse{ + Uri: fmt.Sprintf("host-sets/%s_", plugin.HostSetPrefix), + Item: &pb.HostSet{ + HostCatalogId: hc.GetPublicId(), + Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org.GetPublicId()}, + Plugin: &plugins.PluginInfo{ + Id: plg.GetPublicId(), + Name: plg.GetName(), + Description: plg.GetDescription(), + }, + Name: &wrappers.StringValue{Value: "name"}, + Description: &wrappers.StringValue{Value: "desc"}, + Type: plugin.Subtype.String(), + PreferredEndpoints: prefEndpoints, + AuthorizedActions: testAuthorizedActions[plugin.Subtype], + }, + }, + }, + { + name: "Bad Preferred Endpoints", + req: &pbs.CreateHostSetRequest{Item: &pb.HostSet{ + HostCatalogId: hc.GetPublicId(), + Name: &wrappers.StringValue{Value: "name"}, + Description: &wrappers.StringValue{Value: "desc"}, + Type: plugin.Subtype.String(), + PreferredEndpoints: append(prefEndpoints, "foobar:1.2.3.4"), + }}, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Create with mismatched type/name", + req: &pbs.CreateHostSetRequest{Item: &pb.HostSet{ + HostCatalogId: hc.GetPublicId(), + Name: &wrappers.StringValue{Value: "name"}, + Description: &wrappers.StringValue{Value: "desc"}, + Type: "ThisIsMadeUp", + }}, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Create with no type", + req: &pbs.CreateHostSetRequest{Item: &pb.HostSet{ + HostCatalogId: hc.GetPublicId(), + Name: &wrappers.StringValue{Value: "no type name"}, + Description: &wrappers.StringValue{Value: "no type desc"}, + }}, + res: &pbs.CreateHostSetResponse{ + Uri: fmt.Sprintf("host-sets/%s_", plugin.HostSetPrefix), + Item: &pb.HostSet{ + HostCatalogId: hc.GetPublicId(), + Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org.GetPublicId()}, + Plugin: &plugins.PluginInfo{ + Id: plg.GetPublicId(), + Name: plg.GetName(), + Description: plg.GetDescription(), + }, + Name: &wrappers.StringValue{Value: "no type name"}, + Description: &wrappers.StringValue{Value: "no type desc"}, + Type: plugin.Subtype.String(), + AuthorizedActions: testAuthorizedActions[plugin.Subtype], + }, + }, + }, + { + name: "Can't specify Id", + req: &pbs.CreateHostSetRequest{Item: &pb.HostSet{ + HostCatalogId: hc.GetPublicId(), + Id: "not allowed to be set", + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Can't specify Created Time", + req: &pbs.CreateHostSetRequest{Item: &pb.HostSet{ + HostCatalogId: hc.GetPublicId(), + CreatedTime: timestamppb.Now(), + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Can't specify Update Time", + req: &pbs.CreateHostSetRequest{Item: &pb.HostSet{ + HostCatalogId: hc.GetPublicId(), + UpdatedTime: timestamppb.Now(), + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + s, err := host_sets.NewService(repoFn, plgRepoFn) + require.NoError(err, "Failed to create a new host set service.") + + got, gErr := s.CreateHostSet(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), tc.req) + if tc.err != nil { + require.Error(gErr) + assert.True(errors.Is(gErr, tc.err), "CreateHostSet(%+v) got error %v, wanted %v", tc.req, gErr, tc.err) + return + } + require.NoError(gErr) + if got != nil { + assert.Contains(got.GetUri(), tc.res.GetUri()) + assert.True(strings.HasPrefix(got.GetItem().GetId(), plugin.HostSetPrefix), got.GetItem().GetId()) + gotCreateTime := got.GetItem().GetCreatedTime().AsTime() + require.NoError(err, "Error converting proto to timestamp.") + gotUpdateTime := got.GetItem().GetUpdatedTime().AsTime() + require.NoError(err, "Error converting proto to timestamp") + // Verify it is a set created after the test setup's default set + assert.True(gotCreateTime.After(defaultHcCreated), "New set should have been created after default set. Was created %v, which is after %v", gotCreateTime, defaultHcCreated) + assert.True(gotUpdateTime.After(defaultHcCreated), "New set should have been updated after default set. Was updated %v, which is after %v", gotUpdateTime, defaultHcCreated) + + // Clear all values which are hard to compare against. + got.Uri, tc.res.Uri = "", "" + got.Item.Id, tc.res.Item.Id = "", "" + got.Item.CreatedTime, got.Item.UpdatedTime, tc.res.Item.CreatedTime, tc.res.Item.UpdatedTime = nil, nil, nil, nil + } + if tc.res != nil { + tc.res.Item.Version = 1 + } + assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "CreateHostSet(%q) got response %q, wanted %q", tc.req, got, tc.res) + }) + } +} + +func TestUpdate_Static(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { @@ -513,7 +1109,10 @@ func TestUpdate(t *testing.T) { Id: hs.GetPublicId(), } - tested, err := host_sets.NewService(repoFn) + plgRepoFn := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) + } + tested, err := host_sets.NewService(repoFn, plgRepoFn) require.NoError(t, err, "Failed to create a new host set service.") cases := []struct { @@ -544,7 +1143,7 @@ func TestUpdate(t *testing.T) { CreatedTime: hs.GetCreateTime().GetTimestamp(), Type: "static", HostIds: hIds, - AuthorizedActions: testAuthorizedActions, + AuthorizedActions: testAuthorizedActions[static.Subtype], }, }, }, @@ -570,7 +1169,7 @@ func TestUpdate(t *testing.T) { CreatedTime: hs.GetCreateTime().GetTimestamp(), Type: "static", HostIds: hIds, - AuthorizedActions: testAuthorizedActions, + AuthorizedActions: testAuthorizedActions[static.Subtype], }, }, }, @@ -639,7 +1238,7 @@ func TestUpdate(t *testing.T) { CreatedTime: hs.GetCreateTime().GetTimestamp(), Type: "static", HostIds: hIds, - AuthorizedActions: testAuthorizedActions, + AuthorizedActions: testAuthorizedActions[static.Subtype], }, }, }, @@ -662,7 +1261,7 @@ func TestUpdate(t *testing.T) { CreatedTime: hs.GetCreateTime().GetTimestamp(), Type: "static", HostIds: hIds, - AuthorizedActions: testAuthorizedActions, + AuthorizedActions: testAuthorizedActions[static.Subtype], }, }, }, @@ -687,7 +1286,7 @@ func TestUpdate(t *testing.T) { CreatedTime: hs.GetCreateTime().GetTimestamp(), Type: "static", HostIds: hIds, - AuthorizedActions: testAuthorizedActions, + AuthorizedActions: testAuthorizedActions[static.Subtype], }, }, }, @@ -712,7 +1311,7 @@ func TestUpdate(t *testing.T) { CreatedTime: hs.GetCreateTime().GetTimestamp(), Type: "static", HostIds: hIds, - AuthorizedActions: testAuthorizedActions, + AuthorizedActions: testAuthorizedActions[static.Subtype], }, }, }, @@ -844,10 +1443,316 @@ func TestUpdate(t *testing.T) { } } +func TestUpdate_Plugin(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + _, proj := iam.TestScopes(t, iamRepo) + rw := db.New(conn) + + name := "test" + plg := hostplugin.TestPlugin(t, conn, name) + plgm := map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): plugin.NewWrappingPluginClient(&plugin.TestPluginServer{}), + } + + repoFn := func() (*static.Repository, error) { + return static.NewRepository(rw, rw, kms) + } + pluginHostRepo := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, plgm) + } + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + tested, err := host_sets.NewService(repoFn, pluginHostRepo) + require.NoError(t, err, "Failed to create a new host catalog service.") + + hc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) + ctx := auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()) + + freshSet := func(t *testing.T) *pb.HostSet { + attr, err := structpb.NewStruct(map[string]interface{}{ + "foo": "bar", + }) + require.NoError(t, err) + resp, err := tested.CreateHostSet(ctx, &pbs.CreateHostSetRequest{Item: &pb.HostSet{ + HostCatalogId: hc.GetPublicId(), + Name: wrapperspb.String("default"), + Description: wrapperspb.String("default"), + Type: plugin.Subtype.String(), + Attributes: attr, + PreferredEndpoints: []string{"dns:default"}, + }}) + require.NoError(t, err) + id := resp.GetItem().GetId() + t.Cleanup(func() { + _, err := tested.DeleteHostSet(ctx, &pbs.DeleteHostSetRequest{Id: id}) + require.NoError(t, err) + }) + return resp.GetItem() + } + + type updateFn func(catalog *pb.HostSet) + clearReadOnlyFields := func() updateFn { + return func(c *pb.HostSet) { + c.Id = "" + c.Plugin = nil + c.AuthorizedActions = nil + c.CreatedTime = nil + c.UpdatedTime = nil + } + } + updateName := func(i *wrappers.StringValue) updateFn { + return func(c *pb.HostSet) { + c.Name = i + } + } + updateDesc := func(i *wrappers.StringValue) updateFn { + return func(c *pb.HostSet) { + c.Description = i + } + } + updatePreferedEndpoints := func(i []string) updateFn { + return func(c *pb.HostSet) { + c.PreferredEndpoints = i + } + } + updateAttrs := func(i *structpb.Struct) updateFn { + return func(c *pb.HostSet) { + c.Attributes = i + } + } + + cases := []struct { + name string + masks []string + changes []updateFn + check func(*testing.T, *pb.HostSet) + err error + }{ + { + name: "Update an existing resource", + masks: []string{"name", "description"}, + changes: []updateFn{ + clearReadOnlyFields(), + updateName(wrapperspb.String("new")), + updateDesc(wrapperspb.String("desc")), + }, + check: func(t *testing.T, in *pb.HostSet) { + assert.Equal(t, "new", in.Name.GetValue()) + assert.Equal(t, "desc", in.Description.GetValue()) + }, + }, + { + name: "Update arbitrary attribute", + masks: []string{"attributes.newkey", "attributes.foo"}, + changes: []updateFn{ + clearReadOnlyFields(), + updateAttrs(func() *structpb.Struct { + attr, err := structpb.NewStruct(map[string]interface{}{ + "newkey": "newvalue", + "foo": nil, + }) + require.NoError(t, err) + return attr + }()), + }, + check: func(t *testing.T, in *pb.HostSet) { + assert.Equal(t, map[string]interface{}{"newkey": "newvalue"}, in.GetAttributes().AsMap()) + }, + }, + { + name: "Update preferred endpoints", + masks: []string{"preferred_endpoints"}, + changes: []updateFn{ + clearReadOnlyFields(), + updatePreferedEndpoints([]string{"dns:new"}), + }, + check: func(t *testing.T, in *pb.HostSet) { + assert.Equal(t, in.PreferredEndpoints, []string{"dns:new"}) + }, + }, + { + name: "Don't update preferred_endpoints", + masks: []string{"name"}, + changes: []updateFn{ + clearReadOnlyFields(), + updateName(wrapperspb.String("new")), + updatePreferedEndpoints([]string{"dns:ignored"}), + }, + check: func(t *testing.T, in *pb.HostSet) { + assert.Equal(t, "new", in.Name.GetValue()) + assert.Equal(t, in.PreferredEndpoints, []string{"dns:default"}) + }, + }, + { + name: "Multiple Paths in single string", + masks: []string{"name,description"}, + changes: []updateFn{ + clearReadOnlyFields(), + updateName(wrapperspb.String("new")), + updateDesc(wrapperspb.String("desc")), + }, + check: func(t *testing.T, in *pb.HostSet) { + assert.Equal(t, "new", in.Name.GetValue()) + assert.Equal(t, "desc", in.Description.GetValue()) + }, + }, + { + name: "No Update Mask", + changes: []updateFn{ + clearReadOnlyFields(), + updateName(wrapperspb.String("new")), + updateDesc(wrapperspb.String("desc")), + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Empty Path", + masks: []string{}, + changes: []updateFn{ + clearReadOnlyFields(), + updateName(wrapperspb.String("new")), + updateDesc(wrapperspb.String("desc")), + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Only non-existant paths in Mask", + masks: []string{"nonexistant_field"}, + changes: []updateFn{ + clearReadOnlyFields(), + updateName(wrapperspb.String("new")), + updateDesc(wrapperspb.String("desc")), + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Unset Name", + masks: []string{"name"}, + changes: []updateFn{ + clearReadOnlyFields(), + updateName(nil), + updateDesc(wrapperspb.String("ignored")), + }, + check: func(t *testing.T, in *pb.HostSet) { + assert.Nil(t, in.Name) + assert.Equal(t, "default", in.Description.GetValue()) + }, + }, + { + name: "Unset Description", + masks: []string{"description"}, + changes: []updateFn{ + clearReadOnlyFields(), + updateName(wrapperspb.String("ignored")), + updateDesc(nil), + }, + check: func(t *testing.T, in *pb.HostSet) { + assert.Nil(t, in.Description) + assert.Equal(t, "default", in.Name.GetValue()) + }, + }, + { + name: "Update Only Name", + masks: []string{"name"}, + changes: []updateFn{ + clearReadOnlyFields(), + updateName(wrapperspb.String("updated")), + updateDesc(wrapperspb.String("ignored")), + }, + check: func(t *testing.T, in *pb.HostSet) { + assert.Equal(t, "updated", in.Name.GetValue()) + assert.Equal(t, "default", in.Description.GetValue()) + }, + }, + { + name: "Update Only Description", + masks: []string{"description"}, + changes: []updateFn{ + clearReadOnlyFields(), + updateName(wrapperspb.String("ignored")), + updateDesc(wrapperspb.String("updated")), + }, + check: func(t *testing.T, in *pb.HostSet) { + assert.Equal(t, "default", in.Name.GetValue()) + assert.Equal(t, "updated", in.Description.GetValue()) + }, + }, + { + name: "Cant change type", + masks: []string{"type"}, + changes: []updateFn{ + clearReadOnlyFields(), + func(c *pb.HostSet) { + c.Type = "static" + }, + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Cant specify Updated Time", + masks: []string{"updated_time"}, + changes: []updateFn{ + clearReadOnlyFields(), + func(c *pb.HostSet) { + c.UpdatedTime = timestamppb.Now() + }, + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + hc := freshSet(t) + + id := hc.GetId() + for _, f := range tc.changes { + f(hc) + } + + req := &pbs.UpdateHostSetRequest{ + Id: id, + Item: hc, + UpdateMask: &field_mask.FieldMask{Paths: tc.masks}, + } + + // Test some bad versions + req.Item.Version = 2 + _, gErr := tested.UpdateHostSet(ctx, req) + require.Error(gErr) + req.Item.Version = 0 + _, gErr = tested.UpdateHostSet(ctx, req) + require.Error(gErr) + req.Item.Version = 1 + + got, gErr := tested.UpdateHostSet(ctx, req) + if tc.err != nil { + require.Error(gErr) + assert.True(errors.Is(gErr, tc.err), "UpdateHostSet(%+v) got error %v, wanted %v", req, gErr, tc.err) + return + } + require.NoError(gErr) + require.NotNil(got) + + item := got.GetItem() + assert.Equal(uint32(2), item.GetVersion()) + assert.Greater(item.GetUpdatedTime().AsTime().UnixNano(), item.GetCreatedTime().AsTime().UnixNano()) + tc.check(t, item) + }) + } +} + func TestAddHostSetHosts(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { @@ -860,7 +1765,10 @@ func TestAddHostSetHosts(t *testing.T) { repoFn := func() (*static.Repository, error) { return static.NewRepository(rw, rw, kms) } - s, err := host_sets.NewService(repoFn) + plgRepoFn := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) + } + s, err := host_sets.NewService(repoFn, plgRepoFn) require.NoError(t, err, "Error when getting new host set service.") hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] @@ -965,6 +1873,7 @@ func TestSetHostSetHosts(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { @@ -977,7 +1886,10 @@ func TestSetHostSetHosts(t *testing.T) { repoFn := func() (*static.Repository, error) { return static.NewRepository(rw, rw, kms) } - s, err := host_sets.NewService(repoFn) + plgRepoFn := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) + } + s, err := host_sets.NewService(repoFn, plgRepoFn) require.NoError(t, err, "Error when getting new host set service.") hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] @@ -1078,6 +1990,7 @@ func TestRemoveHostSetHosts(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { @@ -1090,7 +2003,10 @@ func TestRemoveHostSetHosts(t *testing.T) { repoFn := func() (*static.Repository, error) { return static.NewRepository(rw, rw, kms) } - s, err := host_sets.NewService(repoFn) + plgRepoFn := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) + } + s, err := host_sets.NewService(repoFn, plgRepoFn) require.NoError(t, err, "Error when getting new host set service.") hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] diff --git a/internal/servers/controller/handlers/hosts/host_service.go b/internal/servers/controller/handlers/hosts/host_service.go index b5d1ac62a..70ea91358 100644 --- a/internal/servers/controller/handlers/hosts/host_service.go +++ b/internal/servers/controller/handlers/hosts/host_service.go @@ -10,9 +10,11 @@ import ( "github.com/hashicorp/boundary/internal/errors" pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" "github.com/hashicorp/boundary/internal/host" + "github.com/hashicorp/boundary/internal/host/plugin" "github.com/hashicorp/boundary/internal/host/static" "github.com/hashicorp/boundary/internal/host/static/store" "github.com/hashicorp/boundary/internal/perms" + hostplugin "github.com/hashicorp/boundary/internal/plugin/host" "github.com/hashicorp/boundary/internal/requests" "github.com/hashicorp/boundary/internal/servers/controller/auth" "github.com/hashicorp/boundary/internal/servers/controller/common" @@ -21,6 +23,7 @@ import ( "github.com/hashicorp/boundary/internal/types/resource" "github.com/hashicorp/boundary/internal/types/subtypes" pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hosts" + "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/plugins" "google.golang.org/grpc/codes" "google.golang.org/protobuf/types/known/wrapperspb" ) @@ -30,11 +33,17 @@ var ( // IdActions contains the set of actions that can be performed on // individual resources - IdActions = action.ActionSet{ - action.NoOp, - action.Read, - action.Update, - action.Delete, + idActionsTypeMap = map[subtypes.Subtype]action.ActionSet{ + static.Subtype: { + action.NoOp, + action.Read, + action.Update, + action.Delete, + }, + plugin.Subtype: { + action.NoOp, + action.Read, + }, } // CollectionActions contains the set of actions that can be performed on @@ -56,18 +65,22 @@ type Service struct { pbs.UnimplementedHostServiceServer staticRepoFn common.StaticRepoFactory + pluginRepoFn common.PluginHostRepoFactory } var _ pbs.HostServiceServer = Service{} // NewService returns a host Service which handles host related requests to boundary and uses the provided // repositories for storage and retrieval. -func NewService(repoFn common.StaticRepoFactory) (Service, error) { +func NewService(repoFn common.StaticRepoFactory, pluginRepoFn common.PluginHostRepoFactory) (Service, error) { const op = "hosts.NewService" if repoFn == nil { return Service{}, errors.NewDeprecated(errors.InvalidParameter, op, "missing static repository") } - return Service{staticRepoFn: repoFn}, nil + if pluginRepoFn == nil { + return Service{}, errors.NewDeprecated(errors.InvalidParameter, op, "missing plugin host repository") + } + return Service{staticRepoFn: repoFn, pluginRepoFn: pluginRepoFn}, nil } func (s Service) ListHosts(ctx context.Context, req *pbs.ListHostsRequest) (*pbs.ListHostsResponse, error) { @@ -78,7 +91,7 @@ func (s Service) ListHosts(ctx context.Context, req *pbs.ListHostsRequest) (*pbs if authResults.Error != nil { return nil, authResults.Error } - hl, err := s.listFromRepo(ctx, req.GetHostCatalogId()) + hl, plg, err := s.listFromRepo(ctx, req.GetHostCatalogId()) if err != nil { return nil, err } @@ -99,7 +112,8 @@ func (s Service) ListHosts(ctx context.Context, req *pbs.ListHostsRequest) (*pbs } for _, item := range hl { res.Id = item.GetPublicId() - authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.WithResource(&res)).Strings() + idActions := idActionsTypeMap[host.SubtypeFromId(res.Id)] + authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), idActions, auth.WithResource(&res)).Strings() if len(authorizedActions) == 0 { continue } @@ -107,6 +121,9 @@ func (s Service) ListHosts(ctx context.Context, req *pbs.ListHostsRequest) (*pbs outputFields := authResults.FetchOutputFields(res, action.List).SelfOrDefaults(authResults.UserId) outputOpts := make([]handlers.Option, 0, 3) outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields)) + if plg != nil { + outputOpts = append(outputOpts, handlers.WithPlugin(plg)) + } if outputFields.Has(globals.ScopeField) { outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) } @@ -114,7 +131,12 @@ func (s Service) ListHosts(ctx context.Context, req *pbs.ListHostsRequest) (*pbs outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions)) } - item, err := toProto(ctx, item, nil, outputOpts...) + var hostSetIds []string + switch h := item.(type) { + case *plugin.Host: + hostSetIds = h.SetIds + } + item, err := toProto(ctx, item, hostSetIds, outputOpts...) if err != nil { return nil, err } @@ -137,7 +159,7 @@ func (s Service) GetHost(ctx context.Context, req *pbs.GetHostRequest) (*pbs.Get if authResults.Error != nil { return nil, authResults.Error } - h, err := s.getFromRepo(ctx, req.GetId()) + h, plg, err := s.getFromRepo(ctx, req.GetId()) if err != nil { return nil, err } @@ -149,14 +171,23 @@ func (s Service) GetHost(ctx context.Context, req *pbs.GetHostRequest) (*pbs.Get outputOpts := make([]handlers.Option, 0, 3) outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields)) + if plg != nil { + outputOpts = append(outputOpts, handlers.WithPlugin(plg)) + } if outputFields.Has(globals.ScopeField) { outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) } if outputFields.Has(globals.AuthorizedActionsField) { - outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, h.GetPublicId(), IdActions).Strings())) + idActions := idActionsTypeMap[host.SubtypeFromId(req.GetId())] + outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, h.GetPublicId(), idActions).Strings())) } - item, err := toProto(ctx, h, nil, outputOpts...) + var hostSetIds []string + switch h := h.(type) { + case *plugin.Host: + hostSetIds = h.SetIds + } + item, err := toProto(ctx, h, hostSetIds, outputOpts...) if err != nil { return nil, err } @@ -191,7 +222,8 @@ func (s Service) CreateHost(ctx context.Context, req *pbs.CreateHostRequest) (*p outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) } if outputFields.Has(globals.AuthorizedActionsField) { - outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, h.GetPublicId(), IdActions).Strings())) + idActions := idActionsTypeMap[host.SubtypeFromId(req.GetItem().GetHostCatalogId())] + outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, h.GetPublicId(), idActions).Strings())) } item, err := toProto(ctx, h, nil, outputOpts...) @@ -232,7 +264,8 @@ func (s Service) UpdateHost(ctx context.Context, req *pbs.UpdateHostRequest) (*p outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) } if outputFields.Has(globals.AuthorizedActionsField) { - outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, h.GetPublicId(), IdActions).Strings())) + idActions := idActionsTypeMap[host.SubtypeFromId(req.GetId())] + outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, h.GetPublicId(), idActions).Strings())) } item, err := toProto(ctx, h, nil, outputOpts...) @@ -259,19 +292,38 @@ func (s Service) DeleteHost(ctx context.Context, req *pbs.DeleteHostRequest) (*p return nil, nil } -func (s Service) getFromRepo(ctx context.Context, id string) (*static.Host, error) { - repo, err := s.staticRepoFn() - if err != nil { - return nil, err +func (s Service) getFromRepo(ctx context.Context, id string) (host.Host, *plugins.PluginInfo, error) { + var h host.Host + var plg *plugins.PluginInfo + switch host.SubtypeFromId(id) { + case static.Subtype: + repo, err := s.staticRepoFn() + if err != nil { + return nil, nil, err + } + h, err = repo.LookupHost(ctx, id) + if err != nil { + return nil, nil, err + } + if h == nil { + return nil, nil, handlers.NotFoundErrorf("Host %q doesn't exist.", id) + } + case plugin.Subtype: + repo, err := s.pluginRepoFn() + if err != nil { + return nil, nil, err + } + ph, hPlg, err := repo.LookupHost(ctx, id) + if err != nil { + return nil, nil, err + } + if ph == nil { + return nil, nil, handlers.NotFoundErrorf("Host %q doesn't exist.", id) + } + h = ph + plg = toPluginInfo(hPlg) } - h, err := repo.LookupHost(ctx, id) - if err != nil { - return nil, err - } - if h == nil { - return nil, handlers.NotFoundErrorf("Host %q doesn't exist.", id) - } - return h, nil + return h, plg, nil } func (s Service) createInRepo(ctx context.Context, scopeId, catalogId string, item *pb.Host) (*static.Host, error) { @@ -361,21 +413,47 @@ func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool, return rows > 0, nil } -func (s Service) listFromRepo(ctx context.Context, catalogId string) ([]*static.Host, error) { - repo, err := s.staticRepoFn() - if err != nil { - return nil, err +func (s Service) listFromRepo(ctx context.Context, catalogId string) ([]host.Host, *plugins.PluginInfo, error) { + var hosts []host.Host + var plg *plugins.PluginInfo + switch host.SubtypeFromId(catalogId) { + case static.Subtype: + repo, err := s.staticRepoFn() + if err != nil { + return nil, nil, err + } + hl, err := repo.ListHosts(ctx, catalogId) + if err != nil { + return nil, nil, err + } + for _, h := range hl { + hosts = append(hosts, h) + } + case plugin.Subtype: + repo, err := s.pluginRepoFn() + if err != nil { + return nil, nil, err + } + hl, hlPlg, err := repo.ListHostsByCatalogId(ctx, catalogId) + if err != nil { + return nil, nil, err + } + for _, h := range hl { + hosts = append(hosts, h) + } + plg = toPluginInfo(hlPlg) } - hl, err := repo.ListHosts(ctx, catalogId) - if err != nil { - return nil, err - } - return hl, nil + return hosts, plg, nil } -func (s Service) parentAndAuthResult(ctx context.Context, id string, a action.Type) (*static.HostCatalog, auth.VerifyResults) { +func (s Service) parentAndAuthResult(ctx context.Context, id string, a action.Type) (host.Catalog, auth.VerifyResults) { res := auth.VerifyResults{} - repo, err := s.staticRepoFn() + staticRepo, err := s.staticRepoFn() + if err != nil { + res.Error = err + return nil, res + } + pluginRepo, err := s.pluginRepoFn() if err != nil { res.Error = err return nil, res @@ -387,33 +465,72 @@ func (s Service) parentAndAuthResult(ctx context.Context, id string, a action.Ty case action.List, action.Create: parentId = id default: - h, err := repo.LookupHost(ctx, id) + switch host.SubtypeFromId(id) { + case static.Subtype: + h, err := staticRepo.LookupHost(ctx, id) + if err != nil { + res.Error = err + return nil, res + } + if h == nil { + res.Error = handlers.NotFoundError() + return nil, res + } + parentId = h.GetCatalogId() + case plugin.Subtype: + h, _, err := pluginRepo.LookupHost(ctx, id) + if err != nil { + res.Error = err + return nil, res + } + if h == nil { + res.Error = handlers.NotFoundError() + return nil, res + } + parentId = h.GetCatalogId() + } + opts = append(opts, auth.WithId(id)) + } + + var cat host.Catalog + switch host.SubtypeFromId(id) { + case static.Subtype: + cat, err = staticRepo.LookupCatalog(ctx, parentId) if err != nil { res.Error = err return nil, res } - if h == nil { + if cat == nil { + res.Error = handlers.NotFoundError() + return nil, res + } + case plugin.Subtype: + cat, _, err = pluginRepo.LookupCatalog(ctx, parentId) + if err != nil { + res.Error = err + return nil, res + } + if cat == nil { res.Error = handlers.NotFoundError() return nil, res } - parentId = h.GetCatalogId() - opts = append(opts, auth.WithId(id)) - } - - cat, err := repo.LookupCatalog(ctx, parentId) - if err != nil { - res.Error = err - return nil, res - } - if cat == nil { - res.Error = handlers.NotFoundError() - return nil, res } opts = append(opts, auth.WithScopeId(cat.GetScopeId()), auth.WithPin(parentId)) return cat, auth.Verify(ctx, opts...) } -func toProto(ctx context.Context, in *static.Host, hostSets []*static.HostSet, opt ...handlers.Option) (*pb.Host, error) { +func toPluginInfo(plg *hostplugin.Plugin) *plugins.PluginInfo { + if plg == nil { + return nil + } + return &plugins.PluginInfo{ + Id: plg.GetPublicId(), + Name: plg.GetName(), + Description: plg.GetDescription(), + } +} + +func toProto(ctx context.Context, in host.Host, hostSetIds []string, opt ...handlers.Option) (*pb.Host, error) { opts := handlers.GetOpts(opt...) if opts.WithOutputFields == nil { return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building host proto") @@ -428,7 +545,12 @@ func toProto(ctx context.Context, in *static.Host, hostSets []*static.HostSet, o out.HostCatalogId = in.GetCatalogId() } if outputFields.Has(globals.TypeField) { - out.Type = static.Subtype.String() + switch in.(type) { + case *static.Host: + out.Type = static.Subtype.String() + case *plugin.Host: + out.Type = plugin.Subtype.String() + } } if outputFields.Has(globals.DescriptionField) && in.GetDescription() != "" { out.Description = &wrapperspb.StringValue{Value: in.GetDescription()} @@ -443,7 +565,12 @@ func toProto(ctx context.Context, in *static.Host, hostSets []*static.HostSet, o out.UpdatedTime = in.GetUpdateTime().GetTimestamp() } if outputFields.Has(globals.VersionField) { - out.Version = in.GetVersion() + switch in.(type) { + case *static.Host: + out.Version = in.GetVersion() + case *plugin.Host: + out.Version = 1 + } } if outputFields.Has(globals.ScopeField) { out.Scope = opts.WithScope @@ -452,16 +579,31 @@ func toProto(ctx context.Context, in *static.Host, hostSets []*static.HostSet, o out.AuthorizedActions = opts.WithAuthorizedActions } if outputFields.Has(globals.HostSetIdsField) { - for _, hs := range hostSets { - out.HostSetIds = append(out.HostSetIds, hs.GetPublicId()) + for _, hs := range hostSetIds { + out.HostSetIds = append(out.HostSetIds, hs) } } if outputFields.Has(globals.AttributesField) { - st, err := handlers.ProtoToStruct(&pb.StaticHostAttributes{Address: wrapperspb.String(in.GetAddress())}) - if err != nil { - return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to convert static attribute to struct: %s", err) + switch h := in.(type) { + case *static.Host: + st, err := handlers.ProtoToStruct(&pb.StaticHostAttributes{Address: wrapperspb.String(h.GetAddress())}) + if err != nil { + return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to convert static attribute to struct: %s", err) + } + out.Attributes = st + } + } + if outputFields.Has(globals.PluginField) { + out.Plugin = opts.WithPlugin + } + switch h := in.(type) { + case *plugin.Host: + if outputFields.Has(globals.IpAddressesField) { + out.IpAddresses = h.IpAddresses + } + if outputFields.Has(globals.DnsNamesField) { + out.DnsNames = h.DnsNames } - out.Attributes = st } return &out, nil } @@ -481,7 +623,7 @@ func validateGetRequest(req *pbs.GetHostRequest) error { badFields["id"] = "Improperly formatted identifier used." } return badFields - }, req, static.HostPrefix) + }, req, static.HostPrefix, plugin.HostPrefix) } func validateCreateRequest(req *pbs.CreateHostRequest) error { @@ -493,11 +635,17 @@ func validateCreateRequest(req *pbs.CreateHostRequest) error { switch host.SubtypeFromId(req.GetItem().GetHostCatalogId()) { case static.Subtype: if req.GetItem().GetType() != "" && req.GetItem().GetType() != static.Subtype.String() { - badFields["type"] = "Doesn't match the parent resource's type." + badFields[globals.TypeField] = "Doesn't match the parent resource's type." + } + if len(req.GetItem().GetIpAddresses()) > 0 { + badFields[globals.IpAddressesField] = "This field is not supported for this host type." + } + if len(req.GetItem().GetDnsNames()) > 0 { + badFields[globals.DnsNamesField] = "This field is not supported for this host type." } attrs := &pb.StaticHostAttributes{} if err := handlers.StructToProto(req.GetItem().GetAttributes(), attrs); err != nil { - badFields["attributes"] = "Attribute fields do not match the expected format." + badFields[globals.AttributesField] = "Attribute fields do not match the expected format." } if attrs.GetAddress() == nil || len(attrs.GetAddress().GetValue()) < static.MinHostAddressLength || @@ -513,6 +661,8 @@ func validateCreateRequest(req *pbs.CreateHostRequest) error { default: badFields["attributes.address"] = fmt.Sprintf("Error parsing address: %v.", err) } + case plugin.Subtype: + badFields[globals.HostCatalogIdField] = "Cannot manually create hosts for this type of catalog." } return badFields }) @@ -524,8 +674,7 @@ func validateUpdateRequest(req *pbs.UpdateHostRequest) error { switch host.SubtypeFromId(req.GetId()) { case static.Subtype: if req.GetItem().GetType() != "" && req.GetItem().GetType() != static.Subtype.String() { - badFields["type"] = "Cannot modify the resource type." - + badFields[globals.TypeField] = "Cannot modify the resource type." attrs := &pb.StaticHostAttributes{} if err := handlers.StructToProto(req.GetItem().GetAttributes(), attrs); err != nil { badFields["attributes"] = "Attribute fields do not match the expected format." @@ -539,6 +688,8 @@ func validateUpdateRequest(req *pbs.UpdateHostRequest) error { } } } + case plugin.Subtype: + badFields[globals.IdField] = "Cannot modify this type of host." default: badFields["id"] = "Improperly formatted identifier used." } @@ -547,12 +698,19 @@ func validateUpdateRequest(req *pbs.UpdateHostRequest) error { } func validateDeleteRequest(req *pbs.DeleteHostRequest) error { - return handlers.ValidateDeleteRequest(handlers.NoopValidatorFn, req, static.HostPrefix) + return handlers.ValidateDeleteRequest(func() map[string]string { + badFields := map[string]string{} + switch host.SubtypeFromId(req.GetId()) { + case plugin.Subtype: + badFields[globals.IdField] = "Cannot manually delete this type of host." + } + return badFields + }, req, static.HostPrefix) } func validateListRequest(req *pbs.ListHostsRequest) error { badFields := map[string]string{} - if !handlers.ValidId(handlers.Id(req.GetHostCatalogId()), static.HostCatalogPrefix) { + if !handlers.ValidId(handlers.Id(req.GetHostCatalogId()), static.HostCatalogPrefix, plugin.HostCatalogPrefix) { badFields["host_catalog_id"] = "The field is incorrectly formatted." } if _, err := handlers.NewFilter(req.GetFilter()); err != nil { diff --git a/internal/servers/controller/handlers/hosts/host_service_test.go b/internal/servers/controller/handlers/hosts/host_service_test.go index b7cfc7a65..93dbe0d2d 100644 --- a/internal/servers/controller/handlers/hosts/host_service_test.go +++ b/internal/servers/controller/handlers/hosts/host_service_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "sort" "strings" "testing" @@ -11,15 +12,21 @@ import ( "github.com/google/go-cmp/cmp" "github.com/hashicorp/boundary/internal/db" pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" + "github.com/hashicorp/boundary/internal/host/plugin" "github.com/hashicorp/boundary/internal/host/static" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" + hostplugin "github.com/hashicorp/boundary/internal/plugin/host" + "github.com/hashicorp/boundary/internal/scheduler" "github.com/hashicorp/boundary/internal/servers/controller/auth" "github.com/hashicorp/boundary/internal/servers/controller/handlers" "github.com/hashicorp/boundary/internal/servers/controller/handlers/hosts" "github.com/hashicorp/boundary/internal/types/scope" + "github.com/hashicorp/boundary/internal/types/subtypes" pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hosts" + "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/plugins" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/genproto/protobuf/field_mask" @@ -28,11 +35,15 @@ import ( "google.golang.org/protobuf/testing/protocmp" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" ) -var testAuthorizedActions = []string{"no-op", "read", "update", "delete"} +var testAuthorizedActions = map[subtypes.Subtype][]string{ + static.Subtype: {"no-op", "read", "update", "delete"}, + plugin.Subtype: {"no-op", "read"}, +} -func TestGet(t *testing.T) { +func TestGet_Static(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) @@ -46,6 +57,10 @@ func TestGet(t *testing.T) { org, proj := iam.TestScopes(t, iamRepo) rw := db.New(conn) + sche := scheduler.TestScheduler(t, conn, wrapper) + pluginRepoFn := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) + } repoFn := func() (*static.Repository, error) { return static.NewRepository(rw, rw, kms) } @@ -62,7 +77,7 @@ func TestGet(t *testing.T) { Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ "address": structpb.NewStringValue(h.GetAddress()), }}, - AuthorizedActions: testAuthorizedActions, + AuthorizedActions: testAuthorizedActions[static.Subtype], } cases := []struct { @@ -98,8 +113,8 @@ func TestGet(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - s, err := hosts.NewService(repoFn) - require.NoError(err, "Couldn't create a new host set service.") + s, err := hosts.NewService(repoFn, pluginRepoFn) + require.NoError(err, "Couldn't create a new host service.") got, gErr := s.GetHost(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), tc.req) if tc.err != nil { @@ -115,7 +130,106 @@ func TestGet(t *testing.T) { } } -func TestList(t *testing.T) { +func TestGet_Plugin(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + + iamRepo := iam.TestRepo(t, conn, wrapper) + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + + org, proj := iam.TestScopes(t, iamRepo) + + plg := hostplugin.TestPlugin(t, conn, "test") + plgm := map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): plugin.NewWrappingPluginClient(&plugin.TestPluginServer{}), + } + + rw := db.New(conn) + sche := scheduler.TestScheduler(t, conn, wrapper) + pluginRepoFn := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, plgm) + } + repoFn := func() (*static.Repository, error) { + return static.NewRepository(rw, rw, kms) + } + hc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) + h := plugin.TestHost(t, conn, hc.GetPublicId(), "test") + hs := plugin.TestSet(t, conn, kms, sche, hc, plgm) + plugin.TestSetMembers(t, conn, hs.GetPublicId(), []*plugin.Host{h}) + + pHost := &pb.Host{ + HostCatalogId: hc.GetPublicId(), + Id: h.GetPublicId(), + CreatedTime: h.CreateTime.GetTimestamp(), + UpdatedTime: h.UpdateTime.GetTimestamp(), + Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org.GetPublicId()}, + Type: plugin.Subtype.String(), + Plugin: &plugins.PluginInfo{ + Id: plg.GetPublicId(), + Name: plg.GetName(), + Description: plg.GetDescription(), + }, + HostSetIds: []string{hs.GetPublicId()}, + AuthorizedActions: testAuthorizedActions[plugin.Subtype], + } + + cases := []struct { + name string + req *pbs.GetHostRequest + res *pbs.GetHostResponse + err error + }{ + { + name: "Get an Existing Host", + req: &pbs.GetHostRequest{Id: h.GetPublicId()}, + res: &pbs.GetHostResponse{Item: pHost}, + }, + { + name: "non existing", + req: &pbs.GetHostRequest{Id: plugin.HostPrefix + "_DoesntExis"}, + res: nil, + err: handlers.ApiErrorWithCode(codes.NotFound), + }, + { + name: "Wrong id prefix", + req: &pbs.GetHostRequest{Id: "j_1234567890"}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "space in id", + req: &pbs.GetHostRequest{Id: plugin.HostPrefix + "_1 23456789"}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + s, err := hosts.NewService(repoFn, pluginRepoFn) + require.NoError(err, "Couldn't create a new host service.") + + got, gErr := s.GetHost(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), tc.req) + if tc.err != nil { + require.Error(gErr) + assert.True(errors.Is(gErr, tc.err), "GetHost(%+v) got error %v, wanted %v", tc.req, gErr, tc.err) + return + } + require.NoError(gErr) + + if tc.res != nil { + tc.res.Item.Version = 1 + } + assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "GetHost(%q) got response %q, wanted %q", tc.req, got, tc.res) + }) + } +} + +func TestList_Static(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) @@ -128,6 +242,10 @@ func TestList(t *testing.T) { org, proj := iam.TestScopes(t, iamRepo) rw := db.New(conn) + sche := scheduler.TestScheduler(t, conn, wrapper) + pluginRepoFn := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) + } repoFn := func() (*static.Repository, error) { return static.NewRepository(rw, rw, kms) } @@ -146,7 +264,7 @@ func TestList(t *testing.T) { Type: static.Subtype.String(), Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ "address": structpb.NewStringValue(h.GetAddress()), }}, - AuthorizedActions: testAuthorizedActions, + AuthorizedActions: testAuthorizedActions[static.Subtype], }) } @@ -185,7 +303,7 @@ func TestList(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - s, err := hosts.NewService(repoFn) + s, err := hosts.NewService(repoFn, pluginRepoFn) require.NoError(err, "Couldn't create new host set service.") // Test non-anonymous listing @@ -211,6 +329,124 @@ func TestList(t *testing.T) { } } +func TestList_Plugin(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + + iamRepo := iam.TestRepo(t, conn, wrapper) + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + + org, proj := iam.TestScopes(t, iamRepo) + plg := hostplugin.TestPlugin(t, conn, "test") + plgm := map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): plugin.NewWrappingPluginClient(&plugin.TestPluginServer{}), + } + + rw := db.New(conn) + sche := scheduler.TestScheduler(t, conn, wrapper) + pluginRepoFn := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, plgm) + } + repoFn := func() (*static.Repository, error) { + return static.NewRepository(rw, rw, kms) + } + hcs := plugin.TestCatalogs(t, conn, proj.GetPublicId(), plg.GetPublicId(), 2) + hc, hcNoHosts := hcs[0], hcs[1] + hs := plugin.TestSet(t, conn, kms, sche, hc, plgm) + + var wantHs []*pb.Host + for i := 0; i < 10; i++ { + h := plugin.TestHost(t, conn, hc.GetPublicId(), fmt.Sprintf("host %d", i)) + plugin.TestSetMembers(t, conn, hs.GetPublicId(), []*plugin.Host{h}) + wantHs = append(wantHs, &pb.Host{ + Id: h.GetPublicId(), + HostCatalogId: h.GetCatalogId(), + Plugin: &plugins.PluginInfo{ + Id: plg.GetPublicId(), + Name: plg.GetName(), + Description: plg.GetDescription(), + }, + Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org.GetPublicId()}, + CreatedTime: h.GetCreateTime().GetTimestamp(), + UpdatedTime: h.GetUpdateTime().GetTimestamp(), + HostSetIds: []string{hs.GetPublicId()}, + Version: 1, + Type: plugin.Subtype.String(), + AuthorizedActions: testAuthorizedActions[plugin.Subtype], + }) + } + sort.Slice(wantHs, func(i, j int) bool { + return wantHs[i].GetId() < wantHs[j].GetId() + }) + + cases := []struct { + name string + req *pbs.ListHostsRequest + res *pbs.ListHostsResponse + err error + }{ + { + name: "List Many Hosts", + req: &pbs.ListHostsRequest{HostCatalogId: hc.GetPublicId()}, + res: &pbs.ListHostsResponse{Items: wantHs}, + }, + { + name: "List No Hosts", + req: &pbs.ListHostsRequest{HostCatalogId: hcNoHosts.GetPublicId()}, + res: &pbs.ListHostsResponse{}, + }, + { + name: "Filter to One Host", + req: &pbs.ListHostsRequest{HostCatalogId: hc.GetPublicId(), Filter: fmt.Sprintf(`"/item/id"==%q`, wantHs[1].GetId())}, + res: &pbs.ListHostsResponse{Items: wantHs[1:2]}, + }, + { + name: "Filter to No Hosts", + req: &pbs.ListHostsRequest{HostCatalogId: hc.GetPublicId(), Filter: `"/item/id"=="doesnt match"`}, + res: &pbs.ListHostsResponse{}, + }, + { + name: "Filter Bad Format", + req: &pbs.ListHostsRequest{HostCatalogId: hc.GetPublicId(), Filter: `"//id/"=="bad"`}, + err: handlers.InvalidArgumentErrorf("bad format", nil), + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + s, err := hosts.NewService(repoFn, pluginRepoFn) + require.NoError(err, "Couldn't create new host set service.") + + // Test non-anonymous listing + got, gErr := s.ListHosts(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), tc.req) + if tc.err != nil { + require.Error(gErr) + assert.True(errors.Is(gErr, tc.err), "ListHosts(%q) got error %v, wanted %v", tc.req, gErr, tc.err) + return + } + require.NoError(gErr) + + sort.Slice(got.Items, func(i, j int) bool { + return got.Items[i].GetId() < got.Items[j].GetId() + }) + assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListHosts(%q) got response %q, wanted %q", tc.req, got, tc.res) + + // Test anonymous listing + got, gErr = s.ListHosts(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId(), auth.WithUserId(auth.AnonymousUserId)), tc.req) + require.NoError(gErr) + assert.Len(got.Items, len(tc.res.Items)) + for _, item := range got.GetItems() { + require.Nil(item.CreatedTime) + require.Nil(item.UpdatedTime) + require.Zero(item.Version) + } + }) + } +} + func TestDelete(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") @@ -225,13 +461,21 @@ func TestDelete(t *testing.T) { _, proj := iam.TestScopes(t, iamRepo) rw := db.New(conn) + sche := scheduler.TestScheduler(t, conn, wrapper) + pluginRepoFn := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) + } repoFn := func() (*static.Repository, error) { return static.NewRepository(rw, rw, kms) } hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] - s, err := hosts.NewService(repoFn) + plg := hostplugin.TestPlugin(t, conn, "test") + pluginHc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) + pluginH := plugin.TestHost(t, conn, pluginHc.GetPublicId(), "test") + + s, err := hosts.NewService(repoFn, pluginRepoFn) require.NoError(t, err, "Couldn't create a new host set service.") cases := []struct { @@ -248,6 +492,14 @@ func TestDelete(t *testing.T) { Id: h.GetPublicId(), }, }, + { + name: "Delete a plugin Host", + scopeId: proj.GetPublicId(), + req: &pbs.DeleteHostRequest{ + Id: pluginH.GetPublicId(), + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, { name: "Delete bad id Host", scopeId: proj.GetPublicId(), @@ -293,13 +545,17 @@ func TestDelete_twice(t *testing.T) { _, proj := iam.TestScopes(t, iamRepo) rw := db.New(conn) + sche := scheduler.TestScheduler(t, conn, wrapper) + pluginRepoFn := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) + } repoFn := func() (*static.Repository, error) { return static.NewRepository(rw, rw, kms) } hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] - s, err := hosts.NewService(repoFn) + s, err := hosts.NewService(repoFn, pluginRepoFn) require.NoError(err, "Couldn't create a new host set service.") req := &pbs.DeleteHostRequest{ Id: h.GetPublicId(), @@ -326,11 +582,18 @@ func TestCreate(t *testing.T) { org, proj := iam.TestScopes(t, iamRepo) rw := db.New(conn) + sche := scheduler.TestScheduler(t, conn, wrapper) + pluginRepoFn := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) + } repoFn := func() (*static.Repository, error) { return static.NewRepository(rw, rw, kms) } hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] + plg := hostplugin.TestPlugin(t, conn, "test") + pluginHc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) + defaultHcCreated := hc.GetCreateTime().GetTimestamp().AsTime() cases := []struct { @@ -361,10 +624,18 @@ func TestCreate(t *testing.T) { Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ "address": structpb.NewStringValue("123.456.789"), }}, - AuthorizedActions: testAuthorizedActions, + AuthorizedActions: testAuthorizedActions[static.Subtype], }, }, }, + { + name: "Create a plugin Host", + req: &pbs.CreateHostRequest{Item: &pb.Host{ + HostCatalogId: pluginHc.GetPublicId(), + Type: "plugin", + }}, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, { name: "Create with empty address", req: &pbs.CreateHostRequest{Item: &pb.Host{ @@ -420,7 +691,7 @@ func TestCreate(t *testing.T) { Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ "address": structpb.NewStringValue("123.456.789"), }}, - AuthorizedActions: testAuthorizedActions, + AuthorizedActions: testAuthorizedActions[static.Subtype], }, }, }, @@ -454,7 +725,7 @@ func TestCreate(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - s, err := hosts.NewService(repoFn) + s, err := hosts.NewService(repoFn, pluginRepoFn) require.NoError(err, "Failed to create a new host set service.") got, gErr := s.CreateHost(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), tc.req) @@ -498,6 +769,10 @@ func TestUpdate(t *testing.T) { org, proj := iam.TestScopes(t, iamRepo) rw := db.New(conn) + sche := scheduler.TestScheduler(t, conn, wrapper) + pluginRepoFn := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) + } repoFn := func() (*static.Repository, error) { return static.NewRepository(rw, rw, kms) } @@ -525,7 +800,7 @@ func TestUpdate(t *testing.T) { Id: h.GetPublicId(), } - tested, err := hosts.NewService(repoFn) + tested, err := hosts.NewService(repoFn, pluginRepoFn) require.NoError(t, err, "Failed to create a new host set service.") cases := []struct { @@ -558,7 +833,7 @@ func TestUpdate(t *testing.T) { Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ "address": structpb.NewStringValue("defaultaddress"), }}, - AuthorizedActions: testAuthorizedActions, + AuthorizedActions: testAuthorizedActions[static.Subtype], }, }, }, @@ -586,7 +861,7 @@ func TestUpdate(t *testing.T) { Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ "address": structpb.NewStringValue("defaultaddress"), }}, - AuthorizedActions: testAuthorizedActions, + AuthorizedActions: testAuthorizedActions[static.Subtype], }, }, }, @@ -656,7 +931,7 @@ func TestUpdate(t *testing.T) { Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ "address": structpb.NewStringValue("defaultaddress"), }}, - AuthorizedActions: testAuthorizedActions, + AuthorizedActions: testAuthorizedActions[static.Subtype], }, }, }, @@ -681,7 +956,7 @@ func TestUpdate(t *testing.T) { Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ "address": structpb.NewStringValue("defaultaddress"), }}, - AuthorizedActions: testAuthorizedActions, + AuthorizedActions: testAuthorizedActions[static.Subtype], }, }, }, @@ -708,7 +983,7 @@ func TestUpdate(t *testing.T) { Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ "address": structpb.NewStringValue("defaultaddress"), }}, - AuthorizedActions: testAuthorizedActions, + AuthorizedActions: testAuthorizedActions[static.Subtype], }, }, }, @@ -735,7 +1010,7 @@ func TestUpdate(t *testing.T) { Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ "address": structpb.NewStringValue("defaultaddress"), }}, - AuthorizedActions: testAuthorizedActions, + AuthorizedActions: testAuthorizedActions[static.Subtype], }, }, }, @@ -887,3 +1162,42 @@ func TestUpdate(t *testing.T) { }) } } + +func TestUpdate_Plugin(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + + iamRepo := iam.TestRepo(t, conn, wrapper) + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + + _, proj := iam.TestScopes(t, iamRepo) + + rw := db.New(conn) + sche := scheduler.TestScheduler(t, conn, wrapper) + pluginRepoFn := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) + } + repoFn := func() (*static.Repository, error) { + return static.NewRepository(rw, rw, kms) + } + + plg := hostplugin.TestPlugin(t, conn, "test") + hc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) + h := plugin.TestHost(t, conn, hc.GetPublicId(), "test") + + tested, err := hosts.NewService(repoFn, pluginRepoFn) + require.NoError(t, err) + + got, err := tested.UpdateHost(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), &pbs.UpdateHostRequest{ + Id: h.GetPublicId(), + Item: &pb.Host{Name: wrapperspb.String("foo")}, + UpdateMask: &field_mask.FieldMask{Paths: []string{"name"}}, + }) + assert.Nil(t, got) + require.Error(t, err) + assert.True(t, errors.Is(err, handlers.ApiErrorWithCode(codes.InvalidArgument))) +} diff --git a/internal/servers/controller/handlers/mask_manager.go b/internal/servers/controller/handlers/mask_manager.go index 2f319c5a4..ef3f9d26a 100644 --- a/internal/servers/controller/handlers/mask_manager.go +++ b/internal/servers/controller/handlers/mask_manager.go @@ -19,8 +19,9 @@ type ( MaskSource []protoreflect.ProtoMessage ) -// NewMaskManager returns a mask manager that can translate field masks into the first proto from all subsequent -// protos assuming they are both using the mask_mapping custom option. Error is returned if no mappings are +// NewMaskManager returns a mask manager that can translate field masks into +// the first proto from all subsequent protos assuming they are both using the +// mask_mapping custom option. Error is returned if no mappings are // found or if one of the passed protos has a mapping that doesn't reciprocate. func NewMaskManager(dest MaskDestination, src MaskSource) (MaskManager, error) { const op = "handlers.NewMaskManager" @@ -76,14 +77,32 @@ func mapFromProto(ps []protoreflect.ProtoMessage) (map[string]string, error) { return mapping, nil } -// Translate takes a field mask's paths and returns paths translated for the destination's protobuf. -func (m MaskManager) Translate(paths []string) []string { +// Translate takes a field mask's paths and returns paths translated for the +// destination's protobuf. If a translation doesn't exist for a specific path +// entry then nothing is returned for that specific path entry unless +// passedThroughPrefix is used and applies to that entry. +// +// passedThroughPrefix is helpful when the field mask's paths cannot be mapped +// through the mask mapper for some reason (we don't know the mapping at compile +// time, for example) but we still want certain paths to be passed on. If a +// path entry isn't mapped to anything by the MaskManager but does contain +// a prefix which matches a passedThroughPrefix it will be added unmodified +// to the returned value. +func (m MaskManager) Translate(paths []string, passedThroughPrefix ...string) []string { var result []string for _, v := range paths { vSplit := strings.Split(v, ",") for _, v := range vSplit { - if ov, ok := m[strings.TrimSpace(v)]; ok { + candidate := strings.TrimSpace(v) + if ov, ok := m[candidate]; ok { result = append(result, ov) + } else { + for _, pre := range passedThroughPrefix { + if strings.HasPrefix(candidate, pre) { + result = append(result, candidate) + break + } + } } } } diff --git a/internal/servers/controller/handlers/mask_manager_test.go b/internal/servers/controller/handlers/mask_manager_test.go index 1948a1280..a0638be73 100644 --- a/internal/servers/controller/handlers/mask_manager_test.go +++ b/internal/servers/controller/handlers/mask_manager_test.go @@ -17,6 +17,11 @@ func TestMaskManager(t *testing.T) { assert.Equal(t, []string(nil), mm.Translate([]string{"FiRsT_fIeLd"})) assert.Equal(t, []string{"other_second_field"}, mm.Translate([]string{"strangly_formatted_field"})) assert.Equal(t, []string{"other_second_field", "other_field_3"}, mm.Translate([]string{"strangly_formatted_field", "field3"})) + + // the passedThroughPrefix allows fields to be passed through unmodified + assert.Equal(t, []string{"OtherFirstField", "attributes.doesnt_exist"}, mm.Translate([]string{"first_field", "attributes.doesnt_exist"}, "attributes.")) + // the passedThroughPrefix is ignored if the field already matches a mapped value + assert.Equal(t, []string{"other_second_field", "other_field_3"}, mm.Translate([]string{"strangly_formatted_field", "field3"}, "strangely_")) } func TestMaskManager_Split(t *testing.T) { diff --git a/internal/servers/controller/handlers/options.go b/internal/servers/controller/handlers/options.go index 65414c2ca..c41c735df 100644 --- a/internal/servers/controller/handlers/options.go +++ b/internal/servers/controller/handlers/options.go @@ -2,6 +2,7 @@ package handlers import ( "github.com/hashicorp/boundary/internal/perms" + "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/plugins" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes" "google.golang.org/protobuf/types/known/structpb" ) @@ -26,6 +27,7 @@ type options struct { WithUserIsAnonymous bool WithOutputFields *perms.OutputFieldsMap WithScope *scopes.ScopeInfo + WithPlugin *plugins.PluginInfo WithAuthorizedActions []string WithAuthorizedCollectionActions map[string]*structpb.ListValue WithManagedGroupIds []string @@ -69,6 +71,14 @@ func WithScope(scp *scopes.ScopeInfo) Option { } } +// WithPlugin provides an option when creating responses to include the given +// plugin if allowed +func WithPlugin(plg *plugins.PluginInfo) Option { + return func(o *options) { + o.WithPlugin = plg + } +} + // WithAuthorizedActions provides an option when creating responses to include the given // authorized actions if allowed func WithAuthorizedActions(acts []string) Option { diff --git a/internal/servers/controller/handlers/subtypes_test.go b/internal/servers/controller/handlers/subtypes_test.go index ae4e1263b..6c302642c 100644 --- a/internal/servers/controller/handlers/subtypes_test.go +++ b/internal/servers/controller/handlers/subtypes_test.go @@ -54,7 +54,7 @@ func TestStructToProtoToStruct(t *testing.T) { assert.Empty(t, cmp.Diff(wantStruct, st, protocmp.Transform())) newAttr := tc.pb.ProtoReflect().New().Interface() - StructToProto(st, newAttr) + require.NoError(t, StructToProto(st, newAttr)) assert.Equal(t, tc.pb, newAttr) }) } diff --git a/internal/servers/controller/handlers/targets/target_service.go b/internal/servers/controller/handlers/targets/target_service.go index 0cb56b84e..ee01be7b3 100644 --- a/internal/servers/controller/handlers/targets/target_service.go +++ b/internal/servers/controller/handlers/targets/target_service.go @@ -18,6 +18,7 @@ import ( "github.com/hashicorp/boundary/internal/errors" pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" "github.com/hashicorp/boundary/internal/host" + "github.com/hashicorp/boundary/internal/host/plugin" "github.com/hashicorp/boundary/internal/host/static" "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/perms" @@ -94,6 +95,7 @@ type Service struct { iamRepoFn common.IamRepoFactory serversRepoFn common.ServersRepoFactory sessionRepoFn common.SessionRepoFactory + pluginHostRepoFn common.PluginHostRepoFactory staticHostRepoFn common.StaticRepoFactory vaultCredRepoFn common.VaultCredentialRepoFactory kmsCache *kms.Kms @@ -101,37 +103,43 @@ type Service struct { // NewService returns a target service which handles target related requests to boundary. func NewService( + ctx context.Context, kmsCache *kms.Kms, repoFn common.TargetRepoFactory, iamRepoFn common.IamRepoFactory, serversRepoFn common.ServersRepoFactory, sessionRepoFn common.SessionRepoFactory, + pluginHostRepoFn common.PluginHostRepoFactory, staticHostRepoFn common.StaticRepoFactory, vaultCredRepoFn common.VaultCredentialRepoFactory) (Service, error) { const op = "targets.NewService" if repoFn == nil { - return Service{}, errors.NewDeprecated(errors.InvalidParameter, op, "missing target repository") + return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing target repository") } if iamRepoFn == nil { - return Service{}, errors.NewDeprecated(errors.InvalidParameter, op, "missing iam repository") + return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing iam repository") } if serversRepoFn == nil { - return Service{}, errors.NewDeprecated(errors.InvalidParameter, op, "missing servers repository") + return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing servers repository") } if sessionRepoFn == nil { - return Service{}, errors.NewDeprecated(errors.InvalidParameter, op, "missing session repository") + return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing session repository") + } + if pluginHostRepoFn == nil { + return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing plugin host repository") } if staticHostRepoFn == nil { - return Service{}, errors.NewDeprecated(errors.InvalidParameter, op, "missing static host repository") + return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing static host repository") } if vaultCredRepoFn == nil { - return Service{}, errors.NewDeprecated(errors.InvalidParameter, op, "missing vault credential repository") + return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing vault credential repository") } return Service{ repoFn: repoFn, iamRepoFn: iamRepoFn, serversRepoFn: serversRepoFn, sessionRepoFn: sessionRepoFn, + pluginHostRepoFn: pluginHostRepoFn, staticHostRepoFn: staticHostRepoFn, vaultCredRepoFn: vaultCredRepoFn, kmsCache: kmsCache, @@ -943,55 +951,65 @@ func (s Service) AuthorizeSession(ctx context.Context, req *pbs.AuthorizeSession "No workers are available to handle this session, or all have been filtered.") } - // First, fetch all available hosts. Unless one was chosen in the request, - // we will pick one at random. - type compoundHost struct { - hostSetId string - hostId string - } - - var chosenId *compoundHost requestedId := req.GetHostId() staticHostRepo, err := s.staticHostRepoFn() if err != nil { return nil, err } + pluginHostRepo, err := s.pluginHostRepoFn() + if err != nil { + return nil, err + } - hostIds := make([]compoundHost, 0, len(hostSources)*10) - -HostSourceIterationLoop: + var pluginHostSetIds []string + var endpoints []*host.Endpoint for _, hSource := range hostSources { hsId := hSource.Id() + // FIXME: read in type from DB rather than rely on prefix switch host.SubtypeFromId(hsId) { case static.Subtype: - _, hosts, err := staticHostRepo.LookupSet(ctx, hsId) + eps, err := staticHostRepo.Endpoints(ctx, hsId) if err != nil { return nil, err } - for _, host := range hosts { - compoundId := compoundHost{hostSetId: hsId, hostId: host.PublicId} - hostIds = append(hostIds, compoundId) - if host.PublicId == requestedId { - chosenId = &compoundId - break HostSourceIterationLoop - } + endpoints = append(endpoints, eps...) + default: + // Batch the plugin host set ids since each round trip to the plugin + // has the potential to be expensive. + pluginHostSetIds = append(pluginHostSetIds, hsId) + } + } + if len(pluginHostSetIds) > 0 { + eps, err := pluginHostRepo.Endpoints(ctx, pluginHostSetIds) + if err != nil { + return nil, err + } + endpoints = append(endpoints, eps...) + } + + var chosenEndpoint *host.Endpoint + if requestedId != "" { + for _, ep := range endpoints { + if ep.HostId == requestedId { + chosenEndpoint = ep } } - } - if requestedId != "" && chosenId == nil { - // We didn't find it - return nil, handlers.InvalidArgumentErrorf( - "Errors in provided fields.", - map[string]string{ - "host_id": "The requested host id is not available.", - }) - } - if chosenId == nil { - if len(hostIds) == 0 { - // No hosts were found, error - return nil, handlers.NotFoundErrorf("No hosts found from available target host sources.") + if chosenEndpoint == nil { + // We didn't find it + return nil, handlers.InvalidArgumentErrorf( + "Errors in provided fields.", + map[string]string{ + "host_id": "The requested host id is not available.", + }) } - chosenId = &hostIds[rand.Intn(len(hostIds))] + } + + if chosenEndpoint == nil { + if len(endpoints) == 0 { + // No hosts were found, error + return nil, handlers.NotFoundErrorf("No endpoint found from available target host sources.") + } + chosenEndpoint = endpoints[rand.Intn(len(endpoints))] } // Generate the endpoint URL @@ -999,22 +1017,10 @@ HostSourceIterationLoop: Scheme: t.GetType().String(), } defaultPort := t.GetDefaultPort() - var endpointHost string - switch host.SubtypeFromId(chosenId.hostId) { - case static.Subtype: - h, err := staticHostRepo.LookupHost(ctx, chosenId.hostId) - if err != nil { - return nil, errors.New(ctx, errors.InvalidParameter, op, "errors looking up host") - } - endpointHost = h.Address - if endpointHost == "" { - return nil, stderrors.New("host had empty address") - } - } if defaultPort != 0 { - endpointUrl.Host = fmt.Sprintf("%s:%d", endpointHost, defaultPort) + endpointUrl.Host = fmt.Sprintf("%s:%d", chosenEndpoint.Address, defaultPort) } else { - endpointUrl.Host = endpointHost + endpointUrl.Host = chosenEndpoint.Address } var reqs []credential.Request @@ -1031,9 +1037,9 @@ HostSourceIterationLoop: expTime.Seconds += int64(t.GetSessionMaxSeconds()) sessionComposition := session.ComposedOf{ UserId: authResults.UserId, - HostId: chosenId.hostId, + HostId: chosenEndpoint.HostId, TargetId: t.GetPublicId(), - HostSetId: chosenId.hostSetId, + HostSetId: chosenEndpoint.SetId, AuthTokenId: authResults.AuthTokenId, ScopeId: authResults.Scope.Id, Endpoint: endpointUrl.String(), @@ -1125,7 +1131,7 @@ HostSourceIterationLoop: Type: t.GetType().String(), Certificate: sess.Certificate, PrivateKey: privKey, - HostId: chosenId.hostId, + HostId: chosenEndpoint.HostId, Endpoint: endpointUrl.String(), WorkerInfo: workers, ConnectionLimit: t.GetSessionConnectionLimit(), @@ -1144,8 +1150,8 @@ HostSourceIterationLoop: Type: t.GetType().String(), AuthorizationToken: string(encodedMarshaledSad), UserId: authResults.UserId, - HostId: chosenId.hostId, - HostSetId: chosenId.hostSetId, + HostId: chosenEndpoint.HostId, + HostSetId: chosenEndpoint.SetId, Endpoint: endpointUrl.String(), Credentials: creds, } @@ -1727,7 +1733,8 @@ func validateAddSetsRequest(req *pbs.AddTargetHostSetsRequest) error { badFields[globals.HostSetIdsField] = "Must be non-empty." } for _, id := range req.GetHostSetIds() { - if !handlers.ValidId(handlers.Id(id), static.HostSetPrefix) { + if !handlers.ValidId(handlers.Id(id), static.HostSetPrefix) && + !strings.HasPrefix(id, fmt.Sprintf("%s_", plugin.HostSetPrefix)) { badFields[globals.HostSetIdsField] = fmt.Sprintf("Incorrectly formatted host set identifier %q.", id) break } @@ -1747,7 +1754,8 @@ func validateSetSetsRequest(req *pbs.SetTargetHostSetsRequest) error { badFields[globals.VersionField] = "Required field." } for _, id := range req.GetHostSetIds() { - if !handlers.ValidId(handlers.Id(id), static.HostSetPrefix) { + if !handlers.ValidId(handlers.Id(id), static.HostSetPrefix) && + !strings.HasPrefix(id, fmt.Sprintf("%s_", plugin.HostSetPrefix)) { badFields[globals.HostSetIdsField] = fmt.Sprintf("Incorrectly formatted host set identifier %q.", id) break } @@ -1770,7 +1778,8 @@ func validateRemoveSetsRequest(req *pbs.RemoveTargetHostSetsRequest) error { badFields[globals.HostSetIdsField] = "Must be non-empty." } for _, id := range req.GetHostSetIds() { - if !handlers.ValidId(handlers.Id(id), static.HostSetPrefix) { + if !handlers.ValidId(handlers.Id(id), static.HostSetPrefix) && + !strings.HasPrefix(id, fmt.Sprintf("%s_", plugin.HostSetPrefix)) { badFields[globals.HostSetIdsField] = fmt.Sprintf("Incorrectly formatted host set identifier %q.", id) break } @@ -1793,7 +1802,8 @@ func validateAddHostSourcesRequest(req *pbs.AddTargetHostSourcesRequest) error { badFields[globals.HostSourceIdsField] = "Must be non-empty." } for _, id := range req.GetHostSourceIds() { - if !handlers.ValidId(handlers.Id(id), static.HostSetPrefix) { + if !handlers.ValidId(handlers.Id(id), static.HostSetPrefix) && + !strings.HasPrefix(id, fmt.Sprintf("%s_", plugin.HostSetPrefix)) { badFields[globals.HostSourceIdsField] = fmt.Sprintf("Incorrectly formatted host source identifier %q.", id) break } @@ -1813,7 +1823,8 @@ func validateSetHostSourcesRequest(req *pbs.SetTargetHostSourcesRequest) error { badFields[globals.VersionField] = "Required field." } for _, id := range req.GetHostSourceIds() { - if !handlers.ValidId(handlers.Id(id), static.HostSetPrefix) { + if !handlers.ValidId(handlers.Id(id), static.HostSetPrefix) && + !strings.HasPrefix(id, fmt.Sprintf("%s_", plugin.HostSetPrefix)) { badFields[globals.HostSourceIdsField] = fmt.Sprintf("Incorrectly formatted host source identifier %q.", id) break } @@ -1836,7 +1847,8 @@ func validateRemoveHostSourcesRequest(req *pbs.RemoveTargetHostSourcesRequest) e badFields[globals.HostSourceIdsField] = "Must be non-empty." } for _, id := range req.GetHostSourceIds() { - if !handlers.ValidId(handlers.Id(id), static.HostSetPrefix) { + if !handlers.ValidId(handlers.Id(id), static.HostSetPrefix) && + !strings.HasPrefix(id, fmt.Sprintf("%s_", plugin.HostSetPrefix)) { badFields[globals.HostSourceIdsField] = fmt.Sprintf("Incorrectly formatted host source identifier %q.", id) break } diff --git a/internal/servers/controller/handlers/targets/target_service_test.go b/internal/servers/controller/handlers/targets/target_service_test.go index e28ffb64a..33c278252 100644 --- a/internal/servers/controller/handlers/targets/target_service_test.go +++ b/internal/servers/controller/handlers/targets/target_service_test.go @@ -20,9 +20,11 @@ import ( pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" authpb "github.com/hashicorp/boundary/internal/gen/controller/auth" spbs "github.com/hashicorp/boundary/internal/gen/controller/servers/services" + "github.com/hashicorp/boundary/internal/host/plugin" "github.com/hashicorp/boundary/internal/host/static" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/plugin/host" "github.com/hashicorp/boundary/internal/requests" "github.com/hashicorp/boundary/internal/scheduler" "github.com/hashicorp/boundary/internal/servers" @@ -39,6 +41,7 @@ import ( credpb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/credentiallibraries" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes" pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/targets" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" wrapping "github.com/hashicorp/go-kms-wrapping" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -87,10 +90,13 @@ func testService(t *testing.T, conn *db.DB, kms *kms.Kms, wrapper wrapping.Wrapp staticHostRepoFn := func() (*static.Repository, error) { return static.NewRepository(rw, rw, kms) } + pluginHostRepoFn := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) + } credentialRepoFn := func() (*vault.Repository, error) { return vault.NewRepository(rw, rw, kms, sche) } - return targets.NewService(kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, staticHostRepoFn, credentialRepoFn) + return targets.NewService(context.Background(), kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, pluginHostRepoFn, staticHostRepoFn, credentialRepoFn) } func TestGet(t *testing.T) { @@ -978,6 +984,7 @@ func TestAddTargetHostSets(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { @@ -992,6 +999,12 @@ func TestAddTargetHostSets(t *testing.T) { hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] hs := static.TestSets(t, conn, hc.GetPublicId(), 2) + plg := host.TestPlugin(t, conn, "test") + pluginHc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) + pluginHs := plugin.TestSet(t, conn, kms, sche, pluginHc, map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): plugin.NewWrappingPluginClient(&plugin.TestPluginServer{}), + }) + addCases := []struct { name string tar *tcp.Target @@ -1016,6 +1029,18 @@ func TestAddTargetHostSets(t *testing.T) { addHostSets: []string{hs[1].GetPublicId(), hs[1].GetPublicId()}, resultHostSets: []string{hs[0].GetPublicId(), hs[1].GetPublicId()}, }, + { + name: "Add plugin set on empty target", + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "plugin empty"), + addHostSets: []string{pluginHs.GetPublicId()}, + resultHostSets: []string{pluginHs.GetPublicId()}, + }, + { + name: "Add plugin set on populated target", + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "plugin populated", target.WithHostSources([]string{hs[0].GetPublicId()})), + addHostSets: []string{pluginHs.GetPublicId()}, + resultHostSets: []string{hs[0].GetPublicId(), pluginHs.GetPublicId()}, + }, } for _, tc := range addCases { @@ -1094,6 +1119,7 @@ func TestSetTargetHostSets(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { @@ -1108,6 +1134,12 @@ func TestSetTargetHostSets(t *testing.T) { hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] hs := static.TestSets(t, conn, hc.GetPublicId(), 2) + plg := host.TestPlugin(t, conn, "test") + pluginHc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) + pluginHs := plugin.TestSet(t, conn, kms, sche, pluginHc, map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): plugin.NewWrappingPluginClient(&plugin.TestPluginServer{}), + }) + setCases := []struct { name string tar *tcp.Target @@ -1138,6 +1170,18 @@ func TestSetTargetHostSets(t *testing.T) { setHostSets: []string{}, resultHostSets: nil, }, + { + name: "Set plugin set on empty target", + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "plugin empty"), + setHostSets: []string{pluginHs.GetPublicId()}, + resultHostSets: []string{pluginHs.GetPublicId()}, + }, + { + name: "Set plugin set on populated target", + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "plugin populated", target.WithHostSources([]string{hs[0].GetPublicId()})), + setHostSets: []string{pluginHs.GetPublicId()}, + resultHostSets: []string{pluginHs.GetPublicId()}, + }, } for _, tc := range setCases { t.Run(tc.name, func(t *testing.T) { @@ -1204,6 +1248,7 @@ func TestRemoveTargetHostSets(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { @@ -1218,6 +1263,12 @@ func TestRemoveTargetHostSets(t *testing.T) { hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] hs := static.TestSets(t, conn, hc.GetPublicId(), 2) + plg := host.TestPlugin(t, conn, "test") + pluginHc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) + pluginHs := plugin.TestSet(t, conn, kms, sche, pluginHc, map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): plugin.NewWrappingPluginClient(&plugin.TestPluginServer{}), + }) + removeCases := []struct { name string tar *tcp.Target @@ -1249,6 +1300,12 @@ func TestRemoveTargetHostSets(t *testing.T) { removeHosts: []string{hs[0].GetPublicId(), hs[1].GetPublicId()}, resultHosts: []string{}, }, + { + name: "Remove 1 plugin of 2 sets", + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "remove plugin partial", target.WithHostSources([]string{hs[0].GetPublicId(), pluginHs.GetPublicId()})), + removeHosts: []string{pluginHs.GetPublicId()}, + resultHosts: []string{hs[0].GetPublicId()}, + }, } for _, tc := range removeCases { @@ -1332,6 +1389,7 @@ func TestAddTargetHostSources(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { @@ -1346,6 +1404,12 @@ func TestAddTargetHostSources(t *testing.T) { hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] hs := static.TestSets(t, conn, hc.GetPublicId(), 2) + plg := host.TestPlugin(t, conn, "test") + pluginHc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) + pluginHs := plugin.TestSet(t, conn, kms, sche, pluginHc, map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): plugin.NewWrappingPluginClient(&plugin.TestPluginServer{}), + }) + addCases := []struct { name string tar *tcp.Target @@ -1370,6 +1434,18 @@ func TestAddTargetHostSources(t *testing.T) { addHostSources: []string{hs[1].GetPublicId(), hs[1].GetPublicId()}, resultHostSources: []string{hs[0].GetPublicId(), hs[1].GetPublicId()}, }, + { + name: "Add plugin set on empty target", + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "plugin empty"), + addHostSources: []string{pluginHs.GetPublicId()}, + resultHostSources: []string{pluginHs.GetPublicId()}, + }, + { + name: "Add plugin set on populated target", + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "plugin populated", target.WithHostSources([]string{hs[0].GetPublicId()})), + addHostSources: []string{pluginHs.GetPublicId()}, + resultHostSources: []string{hs[0].GetPublicId(), pluginHs.GetPublicId()}, + }, } for _, tc := range addCases { @@ -1448,6 +1524,7 @@ func TestSetTargetHostSources(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { @@ -1462,6 +1539,12 @@ func TestSetTargetHostSources(t *testing.T) { hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] hs := static.TestSets(t, conn, hc.GetPublicId(), 2) + plg := host.TestPlugin(t, conn, "test") + pluginHc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) + pluginHs := plugin.TestSet(t, conn, kms, sche, pluginHc, map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): plugin.NewWrappingPluginClient(&plugin.TestPluginServer{}), + }) + setCases := []struct { name string tar *tcp.Target @@ -1480,6 +1563,12 @@ func TestSetTargetHostSources(t *testing.T) { setHostSources: []string{hs[1].GetPublicId()}, resultHostSources: []string{hs[1].GetPublicId()}, }, + { + name: "Set plugin set on populated target", + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "plugin populated", target.WithHostSources([]string{hs[0].GetPublicId()})), + setHostSources: []string{pluginHs.GetPublicId()}, + resultHostSources: []string{pluginHs.GetPublicId()}, + }, { name: "Set duplicate host set on populated target", tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "duplicate", target.WithHostSources([]string{hs[0].GetPublicId()})), @@ -1558,6 +1647,7 @@ func TestRemoveTargetHostSources(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { @@ -1572,6 +1662,12 @@ func TestRemoveTargetHostSources(t *testing.T) { hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] hs := static.TestSets(t, conn, hc.GetPublicId(), 2) + plg := host.TestPlugin(t, conn, "test") + pluginHc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) + pluginHs := plugin.TestSet(t, conn, kms, sche, pluginHc, map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): plugin.NewWrappingPluginClient(&plugin.TestPluginServer{}), + }) + removeCases := []struct { name string tar *tcp.Target @@ -1591,6 +1687,12 @@ func TestRemoveTargetHostSources(t *testing.T) { removeHostSources: []string{hs[1].GetPublicId()}, resultHostSources: []string{hs[0].GetPublicId()}, }, + { + name: "Remove 1 plugin set of 2 sets", + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "remove plugin partial", target.WithHostSources([]string{hs[0].GetPublicId(), pluginHs.GetPublicId()})), + removeHostSources: []string{pluginHs.GetPublicId()}, + resultHostSources: []string{hs[0].GetPublicId()}, + }, { name: "Remove 1 duplicate set of 2 sets", tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "remove duplicate", target.WithHostSources([]string{hs[0].GetPublicId(), hs[1].GetPublicId()})), @@ -2477,6 +2579,7 @@ func TestRemoveTargetCredentialSources(t *testing.T) { } func TestAuthorizeSession(t *testing.T) { + ctx := context.Background() conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) @@ -2509,9 +2612,36 @@ func TestAuthorizeSession(t *testing.T) { return authtoken.NewRepository(rw, rw, kms) } + plg := host.TestPlugin(t, conn, "test") + plgm := map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): plugin.NewWrappingPluginClient(plugin.TestPluginServer{ + ListHostsFn: func(_ context.Context, req *plgpb.ListHostsRequest) (*plgpb.ListHostsResponse, error) { + var setIds []string + for _, set := range req.GetSets() { + setIds = append(setIds, set.GetId()) + } + return &plgpb.ListHostsResponse{Hosts: []*plgpb.ListHostsResponseHost{ + { + SetIds: setIds, + ExternalId: "test", + IpAddresses: []string{"10.0.0.1", "192.168.0.1"}, + }, + { + SetIds: setIds, + ExternalId: "test2", + IpAddresses: []string{"10.1.1.1", "192.168.1.1"}, + }, + }}, nil + }, + }), + } + pluginHostRepoFn := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, plgm) + } + org, proj := iam.TestScopes(t, iamRepo) at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId()) - ctx := auth.NewVerifierContext(requests.NewRequestContext(context.Background()), + ctx = auth.NewVerifierContext(requests.NewRequestContext(ctx), iamRepoFn, atRepoFn, serversRepoFn, @@ -2526,21 +2656,19 @@ func TestAuthorizeSession(t *testing.T) { _ = iam.TestUserRole(t, conn, r.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, r.GetPublicId(), "id=*;type=*;actions=*") - s, err := targets.NewService(kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, staticHostRepoFn, credentialRepoFn) + s, err := targets.NewService(ctx, kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, pluginHostRepoFn, staticHostRepoFn, credentialRepoFn) require.NoError(t, err) - tar := tcp.TestTarget(t, conn, proj.GetPublicId(), "test") hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] - hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] - _ = static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) + shs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] + _ = static.TestSetMembers(t, conn, shs.GetPublicId(), []*static.Host{h}) - apiTar, err := s.AddTargetHostSets(ctx, &pbs.AddTargetHostSetsRequest{ - Id: tar.GetPublicId(), - Version: tar.GetVersion(), - HostSetIds: []string{hs.GetPublicId()}, - }) - require.NoError(t, err) + phc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) + phs := plugin.TestSet(t, conn, kms, sche, phc, plgm, plugin.WithPreferredEndpoints([]string{"cidr:10.0.0.0/24"})) + + // Sync the boundary db from the plugins + plugin.TestRunSetSync(t, conn, kms, plgm) v := vault.NewTestVaultServer(t) v.MountPKI(t) @@ -2561,95 +2689,135 @@ func TestAuthorizeSession(t *testing.T) { }}) require.NoError(t, err) - _, err = s.AddTargetCredentialSources(ctx, - &pbs.AddTargetCredentialSourcesRequest{ - Id: tar.GetPublicId(), - ApplicationCredentialSourceIds: []string{clsResp.GetItem().GetId()}, - Version: apiTar.GetItem().GetVersion(), + cases := []struct { + name string + hostSourceId string + credSourceId string + wantedHostId string + wantedEndpoint string + }{ + { + name: "static host", + hostSourceId: shs.GetPublicId(), + credSourceId: clsResp.GetItem().GetId(), + wantedHostId: h.GetPublicId(), + wantedEndpoint: h.GetAddress(), + }, + { + name: "plugin host", + hostSourceId: phs.GetPublicId(), + credSourceId: clsResp.GetItem().GetId(), + wantedHostId: "?", + wantedEndpoint: "10.0.0.1", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + tar := tcp.TestTarget(t, conn, proj.GetPublicId(), tc.name) + apiTar, err := s.AddTargetHostSets(ctx, &pbs.AddTargetHostSetsRequest{ + Id: tar.GetPublicId(), + Version: tar.GetVersion(), + HostSetIds: []string{tc.hostSourceId}, + }) + require.NoError(t, err) + _, err = s.AddTargetCredentialSources(ctx, + &pbs.AddTargetCredentialSourcesRequest{ + Id: tar.GetPublicId(), + ApplicationCredentialSourceIds: []string{clsResp.GetItem().GetId()}, + Version: apiTar.GetItem().GetVersion(), + }) + require.NoError(t, err) + + // Tell our DB that there is a worker ready to serve the data + workerService := workers.NewWorkerServiceServer(serversRepoFn, sessionRepoFn, &sync.Map{}, kms) + _, err = workerService.Status(ctx, &spbs.StatusRequest{ + Worker: &spb.Server{ + PrivateId: "testworker", + Address: "localhost:8457", + }, + }) + require.NoError(t, err) + + asRes1, err := s.AuthorizeSession(ctx, &pbs.AuthorizeSessionRequest{ + Id: tar.GetPublicId(), + }) + require.NoError(t, err) + asRes2, err := s.AuthorizeSession(ctx, &pbs.AuthorizeSessionRequest{ + Id: tar.GetPublicId(), + }) + require.NoError(t, err) + assert.NotEmpty(t, cmp.Diff(asRes1.GetItem().GetCredentials(), asRes2.GetItem().GetCredentials(), protocmp.Transform()), + "the credentials aren't unique per request authorized session") + + wantedHostId := tc.wantedHostId + if tc.wantedHostId == "?" { + wantedHostId = asRes2.GetItem().GetHostId() + } + + want := &pb.SessionAuthorization{ + Scope: &scopes.ScopeInfo{ + Id: proj.GetPublicId(), + Type: proj.GetType(), + Name: proj.GetName(), + Description: proj.GetDescription(), + ParentScopeId: proj.GetParentId(), + }, + TargetId: tar.GetPublicId(), + UserId: at.GetIamUserId(), + HostSetId: tc.hostSourceId, + HostId: wantedHostId, + Type: "tcp", + Endpoint: fmt.Sprintf("tcp://%s", tc.wantedEndpoint), + Credentials: []*pb.SessionCredential{{ + CredentialLibrary: &pb.CredentialLibrary{ + Id: clsResp.GetItem().GetId(), + Name: clsResp.GetItem().GetName().GetValue(), + Description: clsResp.GetItem().GetDescription().GetValue(), + CredentialStoreId: store.GetPublicId(), + Type: vault.Subtype.String(), + }, + CredentialSource: &pb.CredentialSource{ + Id: clsResp.GetItem().GetId(), + Name: clsResp.GetItem().GetName().GetValue(), + Description: clsResp.GetItem().GetDescription().GetValue(), + CredentialStoreId: store.GetPublicId(), + Type: vault.Subtype.String(), + }, + }}, + // TODO: validate the contents of the authorization token is what is expected + } + wantSecret := map[string]interface{}{ + "certificate": "-----BEGIN CERTIFICATE-----\n", + "issuing_ca": "-----BEGIN CERTIFICATE-----\n", + "private_key": "-----BEGIN RSA PRIVATE KEY-----\n", + "private_key_type": "rsa", + } + got := asRes1.GetItem() + + require.Len(t, got.GetCredentials(), 1) + + gotCred := got.Credentials[0] + require.NotNil(t, gotCred.Secret) + assert.NotEmpty(t, gotCred.Secret.Raw) + dSec := decodeJsonSecret(t, gotCred.Secret.Raw) + require.NoError(t, err) + require.Equal(t, dSec, gotCred.Secret.Decoded.AsMap()) + for k, v := range wantSecret { + gotV, ok := dSec[k] + require.True(t, ok) + assert.Truef(t, strings.HasPrefix(gotV.(string), v.(string)), "%q:%q doesn't have prefix %q", k, gotV, v) + } + gotCred.Secret = nil + + got.AuthorizationToken, got.SessionId, got.CreatedTime = "", "", nil + assert.Empty(t, cmp.Diff(got, want, protocmp.Transform())) }) - require.NoError(t, err) - - // Tell our DB that there is a worker ready to serve the data - workerService := workers.NewWorkerServiceServer(serversRepoFn, sessionRepoFn, &sync.Map{}, kms) - _, err = workerService.Status(ctx, &spbs.StatusRequest{ - Worker: &spb.Server{ - PrivateId: "testworker", - Address: "localhost:8457", - }, - }) - require.NoError(t, err) - - asRes1, err := s.AuthorizeSession(ctx, &pbs.AuthorizeSessionRequest{ - Id: tar.GetPublicId(), - }) - require.NoError(t, err) - asRes2, err := s.AuthorizeSession(ctx, &pbs.AuthorizeSessionRequest{ - Id: tar.GetPublicId(), - }) - require.NoError(t, err) - assert.NotEmpty(t, cmp.Diff(asRes1.GetItem().GetCredentials(), asRes2.GetItem().GetCredentials(), protocmp.Transform()), - "the credentials aren't unique per request authorized session") - - want := &pb.SessionAuthorization{ - Scope: &scopes.ScopeInfo{ - Id: proj.GetPublicId(), - Type: proj.GetType(), - Name: proj.GetName(), - Description: proj.GetDescription(), - ParentScopeId: proj.GetParentId(), - }, - TargetId: tar.GetPublicId(), - UserId: at.GetIamUserId(), - HostSetId: hs.GetPublicId(), - HostId: h.GetPublicId(), - Type: "tcp", - Endpoint: fmt.Sprintf("tcp://%s", h.GetAddress()), - Credentials: []*pb.SessionCredential{{ - CredentialLibrary: &pb.CredentialLibrary{ - Id: clsResp.GetItem().GetId(), - Name: clsResp.GetItem().GetName().GetValue(), - Description: clsResp.GetItem().GetDescription().GetValue(), - CredentialStoreId: store.GetPublicId(), - Type: vault.Subtype.String(), - }, - CredentialSource: &pb.CredentialSource{ - Id: clsResp.GetItem().GetId(), - Name: clsResp.GetItem().GetName().GetValue(), - Description: clsResp.GetItem().GetDescription().GetValue(), - CredentialStoreId: store.GetPublicId(), - Type: vault.Subtype.String(), - }, - }}, - // TODO: validate the contents of the authorization token is what is expected } - wantSecret := map[string]interface{}{ - "certificate": "-----BEGIN CERTIFICATE-----\n", - "issuing_ca": "-----BEGIN CERTIFICATE-----\n", - "private_key": "-----BEGIN RSA PRIVATE KEY-----\n", - "private_key_type": "rsa", - } - got := asRes1.GetItem() - - require.Len(t, got.GetCredentials(), 1) - - gotCred := got.Credentials[0] - require.NotNil(t, gotCred.Secret) - assert.NotEmpty(t, gotCred.Secret.Raw) - dSec := decodeJsonSecret(t, gotCred.Secret.Raw) - require.NoError(t, err) - require.Equal(t, dSec, gotCred.Secret.Decoded.AsMap()) - for k, v := range wantSecret { - gotV, ok := dSec[k] - require.True(t, ok) - assert.Truef(t, strings.HasPrefix(gotV.(string), v.(string)), "%q:%q doesn't have prefix %q", k, gotV, v) - } - gotCred.Secret = nil - - got.AuthorizationToken, got.SessionId, got.CreatedTime = "", "", nil - assert.Empty(t, cmp.Diff(got, want, protocmp.Transform())) } func TestAuthorizeSession_Errors(t *testing.T) { + ctx := context.Background() conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) @@ -2675,6 +2843,9 @@ func TestAuthorizeSession_Errors(t *testing.T) { staticHostRepoFn := func() (*static.Repository, error) { return static.NewRepository(rw, rw, kms) } + pluginHostRepoFn := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) + } credentialRepoFn := func() (*vault.Repository, error) { return vault.NewRepository(rw, rw, kms, sche) } @@ -2683,12 +2854,12 @@ func TestAuthorizeSession_Errors(t *testing.T) { } org, proj := iam.TestScopes(t, iamRepo) - s, err := targets.NewService(kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, staticHostRepoFn, credentialRepoFn) + s, err := targets.NewService(ctx, kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, pluginHostRepoFn, staticHostRepoFn, credentialRepoFn) require.NoError(t, err) // Authorized user gets full permissions at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId()) - ctx := auth.NewVerifierContext(requests.NewRequestContext(context.Background()), + ctx = auth.NewVerifierContext(requests.NewRequestContext(context.Background()), iamRepoFn, atRepoFn, serversRepoFn, diff --git a/internal/servers/controller/interceptor.go b/internal/servers/controller/interceptor.go index 2f741f8b1..5eec995fb 100644 --- a/internal/servers/controller/interceptor.go +++ b/internal/servers/controller/interceptor.go @@ -274,7 +274,6 @@ func workerRequestInfoInterceptor(ctx context.Context, eventer *event.Eventer) ( const op = "worker.requestInfoInterceptor" if eventer == nil { return nil, errors.New(ctx, errors.InvalidParameter, op, "missing eventer") - } return func(interceptorCtx context.Context, req interface{}, diff --git a/internal/servers/controller/testing.go b/internal/servers/controller/testing.go index 1d48a6461..1a20eb842 100644 --- a/internal/servers/controller/testing.go +++ b/internal/servers/controller/testing.go @@ -20,6 +20,7 @@ import ( "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/db/schema" "github.com/hashicorp/boundary/internal/gen/testing/interceptor" + "github.com/hashicorp/boundary/internal/host/plugin" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/intglobals" "github.com/hashicorp/boundary/internal/kms" @@ -384,6 +385,10 @@ type TestControllerOpts struct { // The amount of time to wait before marking connections as closed when a // worker has not reported in StatusGracePeriodDuration time.Duration + + // The amount of time between the scheduler waking up to run it's registered + // jobs. + SchedulerRunJobInterval time.Duration } func NewTestController(t *testing.T, opts *TestControllerOpts) *TestController { @@ -477,6 +482,7 @@ func NewTestController(t *testing.T, opts *TestControllerOpts) *TestController { t.Fatal(err) } } + opts.Config.Controller.SchedulerRunJobInterval = opts.SchedulerRunJobInterval if err := tc.b.SetupEventing(tc.b.Logger, tc.b.StderrLock, opts.Config.Controller.Name, base.WithEventerConfig(opts.Config.Eventing)); err != nil { t.Fatal(err) @@ -581,6 +587,7 @@ func NewTestController(t *testing.T, opts *TestControllerOpts) *TestController { } } else if !opts.DisableDatabaseCreation { var createOpts []base.Option + createOpts = append(createOpts, base.WithHostPlugin("pl_1234567890", plugin.NewWrappingPluginClient(plugin.NewLoopbackPlugin()))) if opts.DisableAuthMethodCreation { createOpts = append(createOpts, base.WithSkipAuthMethodCreation()) } diff --git a/internal/tests/api/hostcatalogs/host_catalog_test.go b/internal/tests/api/hostcatalogs/host_catalog_test.go index e37dbe13b..b23d1a4b8 100644 --- a/internal/tests/api/hostcatalogs/host_catalog_test.go +++ b/internal/tests/api/hostcatalogs/host_catalog_test.go @@ -59,6 +59,52 @@ func TestList(t *testing.T) { assert.Equal(filterItem.Id, ul.Items[0].Id) } +func TestList_Plugin(t *testing.T) { + assert, require := assert.New(t), require.New(t) + tc := controller.NewTestController(t, nil) + defer tc.Shutdown() + + client := tc.Client() + token := tc.Token() + client.SetToken(token.Token) + _, proj := iam.TestScopes(t, tc.IamRepo(), iam.WithUserId(token.UserId)) + catalogClient := hostcatalogs.NewClient(client) + + ul, err := catalogClient.List(tc.Context(), proj.GetPublicId()) + require.NoError(err) + assert.Empty(ul.Items) + + var expected []*hostcatalogs.HostCatalog + for i := 0; i < 10; i++ { + expected = append(expected, &hostcatalogs.HostCatalog{Name: fmt.Sprint(i)}) + } + + cr, err := catalogClient.Create(tc.Context(), "plugin", proj.GetPublicId(), + hostcatalogs.WithName(expected[0].Name), hostcatalogs.WithPluginId("pl_1234567890")) + require.NoError(err) + expected[0] = cr.Item + + ul, err = catalogClient.List(tc.Context(), proj.GetPublicId()) + require.NoError(err) + assert.ElementsMatch(comparableCatalogSlice(expected[:1]), comparableCatalogSlice(ul.Items)) + + for i := 1; i < 10; i++ { + cr, err = catalogClient.Create(tc.Context(), "static", proj.GetPublicId(), hostcatalogs.WithName(expected[i].Name)) + require.NoError(err) + expected[i] = cr.Item + } + ul, err = catalogClient.List(tc.Context(), proj.GetPublicId()) + require.NoError(err) + assert.ElementsMatch(comparableCatalogSlice(expected), comparableCatalogSlice(ul.Items)) + + filterItem := ul.Items[3] + ul, err = catalogClient.List(tc.Context(), proj.GetPublicId(), + hostcatalogs.WithFilter(fmt.Sprintf(`"/item/id"==%q`, filterItem.Id))) + require.NoError(err) + assert.Len(ul.Items, 1) + assert.Equal(filterItem.Id, ul.Items[0].Id) +} + func comparableCatalogSlice(in []*hostcatalogs.HostCatalog) []hostcatalogs.HostCatalog { var filtered []hostcatalogs.HostCatalog for _, i := range in { @@ -103,6 +149,9 @@ func TestCrud(t *testing.T) { hc, err = hcClient.Read(tc.Context(), hc.Item.Id) checkCatalog("read", hc.Item, err, "foo", 1) + hc, err = hcClient.Update(tc.Context(), hc.Item.Id, hc.Item.Version, hostcatalogs.WithName("foo")) + checkCatalog("update", hc.Item, err, "foo", 1) + hc, err = hcClient.Update(tc.Context(), hc.Item.Id, hc.Item.Version, hostcatalogs.WithName("bar")) checkCatalog("update", hc.Item, err, "bar", 2) @@ -117,6 +166,41 @@ func TestCrud(t *testing.T) { apiErr := api.AsServerError(err) assert.NotNil(apiErr) assert.EqualValues(http.StatusNotFound, apiErr.Response().StatusCode()) + + // Plugin catalogs + c, err := hcClient.Create(tc.Context(), "plugin", proj.GetPublicId(), hostcatalogs.WithName("pluginfoo"), hostcatalogs.WithPluginId("pl_1234567890"), + hostcatalogs.WithAttributes(map[string]interface{}{"foo": "bar"})) + require.NoError(err) + + c, err = hcClient.Update(tc.Context(), c.Item.Id, c.Item.Version, hostcatalogs.WithName("bar"), + hostcatalogs.WithAttributes(map[string]interface{}{"key": "val", "foo": nil}), + hostcatalogs.WithSecrets(map[string]interface{}{"secretkey": "secretval"})) + checkCatalog("update", c.Item, err, "bar", 2) + assert.Contains(c.Item.Attributes, "key") + assert.NotContains(c.Item.Attributes, "foo") + assert.Empty(c.Item.Secrets) + + c, err = hcClient.Update(tc.Context(), c.Item.Id, c.Item.Version, hostcatalogs.DefaultName()) + checkCatalog("update", c.Item, err, "", 3) + + c, err = hcClient.Update(tc.Context(), c.Item.Id, 0, hostcatalogs.WithAutomaticVersioning(true), + hostcatalogs.WithSecrets(map[string]interface{}{ + "key1": "val1", + "key2": "val2", + })) + checkCatalog("update", c.Item, err, "", 4) + + c, err = hcClient.Read(tc.Context(), c.Item.Id) + assert.NoError(err) + + _, err = hcClient.Delete(tc.Context(), c.Item.Id) + assert.NoError(err) + + _, err = hcClient.Delete(tc.Context(), c.Item.Id) + require.Error(err) + apiErr = api.AsServerError(err) + assert.NotNil(apiErr) + assert.EqualValues(http.StatusNotFound, apiErr.Response().StatusCode()) } func TestErrors(t *testing.T) { diff --git a/internal/tests/api/hosts/host_test.go b/internal/tests/api/hosts/host_test.go index 85d8ac0b0..688fdee10 100644 --- a/internal/tests/api/hosts/host_test.go +++ b/internal/tests/api/hosts/host_test.go @@ -4,10 +4,12 @@ import ( "fmt" "net/http" "testing" + "time" "github.com/hashicorp/boundary/api" "github.com/hashicorp/boundary/api/hostcatalogs" "github.com/hashicorp/boundary/api/hosts" + "github.com/hashicorp/boundary/api/hostsets" "github.com/hashicorp/boundary/internal/host/static" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/servers/controller" @@ -80,6 +82,53 @@ func comparableHostSlice(in []*hosts.Host) []hosts.Host { return filtered } +func TestPluginHosts(t *testing.T) { + assert, require := assert.New(t), require.New(t) + tc := controller.NewTestController(t, &controller.TestControllerOpts{SchedulerRunJobInterval: 100 * time.Millisecond}) + defer tc.Shutdown() + + client := tc.Client() + token := tc.Token() + client.SetToken(token.Token) + _, proj := iam.TestScopes(t, tc.IamRepo(), iam.WithUserId(token.UserId)) + + hc, err := hostcatalogs.NewClient(client).Create(tc.Context(), "plugin", proj.GetPublicId(), + hostcatalogs.WithPluginId("pl_1234567890")) + require.NotNil(hc) + + hset, err := hostsets.NewClient(client).Create(tc.Context(), hc.Item.Id, hostsets.WithAttributes(map[string]interface{}{ + "host_info": []interface{}{ + map[string]interface{}{ + "external_id": "test1", + "ip_addresses": []string{"10.0.0.1", "192.168.1.1"}, + "dns_names": []string{"foo.hashicorp.com", "boundaryproject.io"}, + }, + map[string]interface{}{ + "external_id": "test2", + "ip_addresses": []string{"10.0.0.2", "192.168.1.2"}, + "dns_names": []string{"foo2.hashicorp.com", "boundaryproject2.io"}, + }, + }, + })) + require.NoError(err) + require.NotNil(hset) + time.Sleep(1 * time.Second) + + hClient := hosts.NewClient(client) + hl, err := hClient.List(tc.Context(), hc.Item.Id) + require.NoError(err) + require.Len(hl.Items, 2) + + h, err := hClient.Read(tc.Context(), hl.Items[0].Id) + require.NoError(err) + assert.Equal(hl.Items[0], h.Item) + + _, err = hClient.Update(tc.Context(), h.Item.Id, h.Item.Version, hosts.WithName("foo")) + require.Error(err) + _, err = hClient.Delete(tc.Context(), h.Item.Id) + require.Error(err) +} + func TestCrud(t *testing.T) { assert, require := assert.New(t), require.New(t) tc := controller.NewTestController(t, nil) diff --git a/internal/tests/api/hostsets/host_set_test.go b/internal/tests/api/hostsets/host_set_test.go index e44501839..dcfbdbb8c 100644 --- a/internal/tests/api/hostsets/host_set_test.go +++ b/internal/tests/api/hostsets/host_set_test.go @@ -102,6 +102,57 @@ func TestList(t *testing.T) { assert.Equal(filterItem.Id, ul.Items[0].Id) } +func TestList_Plugin(t *testing.T) { + assert, require := assert.New(t), require.New(t) + tc := controller.NewTestController(t, nil) + defer tc.Shutdown() + + client := tc.Client() + token := tc.Token() + client.SetToken(token.Token) + _, proj := iam.TestScopes(t, tc.IamRepo(), iam.WithUserId(token.UserId)) + + hc, err := hostcatalogs.NewClient(client).Create(tc.Context(), "plugin", proj.GetPublicId(), + hostcatalogs.WithPluginId("pl_1234567890")) + require.NoError(err) + require.NotNil(hc) + + hClient := hostsets.NewClient(client) + + ul, err := hClient.List(tc.Context(), hc.Item.Id) + require.NoError(err) + assert.Empty(ul.Items) + + var expected []*hostsets.HostSet + for i := 0; i < 10; i++ { + expected = append(expected, &hostsets.HostSet{Name: fmt.Sprint(i)}) + } + + hcr, err := hClient.Create(tc.Context(), hc.Item.Id, hostsets.WithName(expected[0].Name)) + require.NoError(err) + expected[0] = hcr.Item + + ul, err = hClient.List(tc.Context(), hc.Item.Id) + require.NoError(err) + assert.ElementsMatch(comparableSetSlice(expected[:1]), comparableSetSlice(ul.Items)) + + for i := 1; i < 10; i++ { + hcr, err = hClient.Create(tc.Context(), hc.Item.Id, hostsets.WithName(expected[i].Name)) + require.NoError(err) + expected[i] = hcr.Item + } + ul, err = hClient.List(tc.Context(), hc.Item.Id) + require.NoError(err) + assert.ElementsMatch(comparableSetSlice(expected), comparableSetSlice(ul.Items)) + + filterItem := ul.Items[3] + ul, err = hClient.List(tc.Context(), hc.Item.Id, + hostsets.WithFilter(fmt.Sprintf(`"/item/id"==%q`, filterItem.Id))) + require.NoError(err) + assert.Len(ul.Items, 1) + assert.Equal(filterItem.Id, ul.Items[0].Id) +} + func comparableSetSlice(in []*hostsets.HostSet) []hostsets.HostSet { var filtered []hostsets.HostSet for _, i := range in { @@ -127,7 +178,8 @@ func TestCrud(t *testing.T) { client.SetToken(token.Token) _, proj := iam.TestScopes(t, tc.IamRepo(), iam.WithUserId(token.UserId)) - hc, err := hostcatalogs.NewClient(client).Create(tc.Context(), "static", proj.GetPublicId()) + hcClient := hostcatalogs.NewClient(client) + hc, err := hcClient.Create(tc.Context(), "static", proj.GetPublicId()) require.NoError(err) require.NotNil(hc) @@ -164,6 +216,34 @@ func TestCrud(t *testing.T) { apiErr := api.AsServerError(err) require.NotNil(apiErr) assert.EqualValues(http.StatusNotFound, apiErr.Response().StatusCode()) + + // Plugin Sets + c, err := hcClient.Create(tc.Context(), "plugin", proj.GetPublicId(), hostcatalogs.WithName("pluginfoo"), hostcatalogs.WithPluginId("pl_1234567890"), + hostcatalogs.WithAttributes(map[string]interface{}{"foo": "bar"})) + require.NoError(err) + + h, err = hClient.Create(tc.Context(), c.Item.Id, hostsets.WithName("foo"), + hostsets.WithAttributes(map[string]interface{}{"foo": "bar"}), hostsets.WithPreferredEndpoints([]string{"dns:test"})) + checkHost(t, "create", h.Item, err, "foo", 1) + + h, err = hClient.Read(tc.Context(), h.Item.Id) + checkHost(t, "read", h.Item, err, "foo", 1) + + h, err = hClient.Update(tc.Context(), h.Item.Id, h.Item.Version, hostsets.WithName("bar"), + hostsets.WithAttributes(map[string]interface{}{"foo": nil, "key": "val"}), + hostsets.WithPreferredEndpoints([]string{"dns:update"})) + checkHost(t, "update", h.Item, err, "bar", 2) + + assert.Equal(h.Item.Attributes, map[string]interface{}{"key": "val"}) + assert.Equal(h.Item.PreferredEndpoints, []string{"dns:update"}) + + _, err = hClient.Delete(tc.Context(), h.Item.Id) + assert.NoError(err) + _, err = hClient.Delete(tc.Context(), h.Item.Id) + require.Error(err) + apiErr = api.AsServerError(err) + require.NotNil(apiErr) + assert.EqualValues(http.StatusNotFound, apiErr.Response().StatusCode()) } // TODO: Get better coverage for expected errors and error formats. diff --git a/packages-oss.lock/pkgs.yml b/packages-oss.lock/pkgs.yml index b89d95cac..21db8e666 100644 --- a/packages-oss.lock/pkgs.yml +++ b/packages-oss.lock/pkgs.yml @@ -1,11 +1,11 @@ # *** # WARNING: Do not EDIT or MERGE this file, it is generated by packagespec. # *** -lockid: 8551e55f6857ec68 +lockid: 7f1510c592a3019a packagespec-version: 0.2.6 cache-version: 10 packages: - - packagespecid: d31a4f1f3856b35a8a0c1dd5eb6a34f62026530f + - packagespecid: 5e182eecadd802ce4fde19b40733c21f1920d680 inputs: BINARY_NAME: boundary BUILD_TAGS: ui @@ -33,7 +33,7 @@ packages: - type: base name: 00-base-cc87158f920a83b1c241d68474eed6734b32c511 archive: .buildcache/archives/00-base-cc87158f920a83b1c241d68474eed6734b32c511.tar.gz - PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-d31a4f1f3856b35a8a0c1dd5eb6a34f62026530f + PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-5e182eecadd802ce4fde19b40733c21f1920d680 POST_PROCESSORS: apple-notarize av-scan circleci: BUILDER_CACHE_KEY_PREFIX_LIST: @@ -41,14 +41,57 @@ packages: - go-modules_74c0_{{checksum ".buildcache/cache-keys/go-modules-74c0c94188690e22d738cb1784f8feba7c3dc00d"}} - ui_dfc9_{{checksum ".buildcache/cache-keys/ui-dfc937906b9b645655c44bc782ef13c64ebe8dbb"}} - base_cc87_{{checksum ".buildcache/cache-keys/base-cc87158f920a83b1c241d68474eed6734b32c511"}} - PACKAGE_CACHE_KEY: package-d31a4f1f3856b35a8a0c1dd5eb6a34f62026530f-{{checksum ".buildcache/cache-keys/package-d31a4f1f3856b35a8a0c1dd5eb6a34f62026530f"}} - build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary + PACKAGE_CACHE_KEY: package-5e182eecadd802ce4fde19b40733c21f1920d680-{{checksum ".buildcache/cache-keys/package-5e182eecadd802ce4fde19b40733c21f1920d680"}} + build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; ORIG_PATH=$(pwd); for PLUGIN_TYPE in host; do for CURR_PLUGIN in $(ls $ORIG_PATH/plugins/$PLUGIN_TYPE/mains); do cd $ORIG_PATH/plugins/$PLUGIN_TYPE/mains/$CURR_PLUGIN; go build -v -o $ORIG_PATH/plugins/$PLUGIN_TYPE/assets/boundary-plugin-${PLUGIN_TYPE}-${CURR_PLUGIN} .; cd $ORIG_PATH; done; cd $ORIG_PATH/plugins/$PLUGIN_TYPE/assets; for CURR_PLUGIN in $(ls); do gzip -9 $CURR_PLUGIN; done; cd $ORIG_PATH; done; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary aliases: - type: local path: boundary_0.6.2_darwin_amd64.zip - type: public-hc-releases path: boundary/boundary_0.6.2/boundary_0.6.2_darwin_amd64.zip - - packagespecid: a315309198e0c87a3964c4a0514206e1a1601de5 + - packagespecid: 72ea9453ed75bfab19ee6a1fd34e7ac751e5e71d + inputs: + BINARY_NAME: boundary + BUILD_TAGS: ui + CGO_ENABLED: "0" + GOARCH: arm64 + GOOS: darwin + PRODUCT_NAME: boundary + PRODUCT_VERSION: 0.6.2 + PRODUCT_VERSION_MMP: 0.6.2 + PRODUCT_VERSION_PRE: "" + meta: + BUILD_JOB_NAME: darwin_arm64_package + BUNDLE_NAME: boundary_0.6.2 + builtin: + BUILD_LAYERS: + - type: copy-source + name: 03-copy-source-6be6bbcf469c2d26620194eeee87f0c4ba470bde + archive: .buildcache/archives/03-copy-source-6be6bbcf469c2d26620194eeee87f0c4ba470bde.tar.gz + - type: go-modules + name: 02-go-modules-74c0c94188690e22d738cb1784f8feba7c3dc00d + archive: .buildcache/archives/02-go-modules-74c0c94188690e22d738cb1784f8feba7c3dc00d.tar.gz + - type: ui + name: 01-ui-dfc937906b9b645655c44bc782ef13c64ebe8dbb + archive: .buildcache/archives/01-ui-dfc937906b9b645655c44bc782ef13c64ebe8dbb.tar.gz + - type: base + name: 00-base-cc87158f920a83b1c241d68474eed6734b32c511 + archive: .buildcache/archives/00-base-cc87158f920a83b1c241d68474eed6734b32c511.tar.gz + PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-72ea9453ed75bfab19ee6a1fd34e7ac751e5e71d + POST_PROCESSORS: apple-notarize av-scan + circleci: + BUILDER_CACHE_KEY_PREFIX_LIST: + - copy-source_6be6_{{checksum ".buildcache/cache-keys/copy-source-6be6bbcf469c2d26620194eeee87f0c4ba470bde"}} + - go-modules_74c0_{{checksum ".buildcache/cache-keys/go-modules-74c0c94188690e22d738cb1784f8feba7c3dc00d"}} + - ui_dfc9_{{checksum ".buildcache/cache-keys/ui-dfc937906b9b645655c44bc782ef13c64ebe8dbb"}} + - base_cc87_{{checksum ".buildcache/cache-keys/base-cc87158f920a83b1c241d68474eed6734b32c511"}} + PACKAGE_CACHE_KEY: package-72ea9453ed75bfab19ee6a1fd34e7ac751e5e71d-{{checksum ".buildcache/cache-keys/package-72ea9453ed75bfab19ee6a1fd34e7ac751e5e71d"}} + build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; ORIG_PATH=$(pwd); for PLUGIN_TYPE in host; do for CURR_PLUGIN in $(ls $ORIG_PATH/plugins/$PLUGIN_TYPE/mains); do cd $ORIG_PATH/plugins/$PLUGIN_TYPE/mains/$CURR_PLUGIN; go build -v -o $ORIG_PATH/plugins/$PLUGIN_TYPE/assets/boundary-plugin-${PLUGIN_TYPE}-${CURR_PLUGIN} .; cd $ORIG_PATH; done; cd $ORIG_PATH/plugins/$PLUGIN_TYPE/assets; for CURR_PLUGIN in $(ls); do gzip -9 $CURR_PLUGIN; done; cd $ORIG_PATH; done; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary + aliases: + - type: local + path: boundary_0.6.2_darwin_arm64.zip + - type: public-hc-releases + path: boundary/boundary_0.6.2/boundary_0.6.2_darwin_arm64.zip + - packagespecid: ccae35a6ffa3283396cbe5608aa626a65cf951f4 inputs: BINARY_NAME: boundary BUILD_TAGS: ui @@ -76,7 +119,7 @@ packages: - type: base name: 00-base-cc87158f920a83b1c241d68474eed6734b32c511 archive: .buildcache/archives/00-base-cc87158f920a83b1c241d68474eed6734b32c511.tar.gz - PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-a315309198e0c87a3964c4a0514206e1a1601de5 + PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-ccae35a6ffa3283396cbe5608aa626a65cf951f4 POST_PROCESSORS: av-scan circleci: BUILDER_CACHE_KEY_PREFIX_LIST: @@ -84,14 +127,14 @@ packages: - go-modules_74c0_{{checksum ".buildcache/cache-keys/go-modules-74c0c94188690e22d738cb1784f8feba7c3dc00d"}} - ui_dfc9_{{checksum ".buildcache/cache-keys/ui-dfc937906b9b645655c44bc782ef13c64ebe8dbb"}} - base_cc87_{{checksum ".buildcache/cache-keys/base-cc87158f920a83b1c241d68474eed6734b32c511"}} - PACKAGE_CACHE_KEY: package-a315309198e0c87a3964c4a0514206e1a1601de5-{{checksum ".buildcache/cache-keys/package-a315309198e0c87a3964c4a0514206e1a1601de5"}} - build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary + PACKAGE_CACHE_KEY: package-ccae35a6ffa3283396cbe5608aa626a65cf951f4-{{checksum ".buildcache/cache-keys/package-ccae35a6ffa3283396cbe5608aa626a65cf951f4"}} + build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; ORIG_PATH=$(pwd); for PLUGIN_TYPE in host; do for CURR_PLUGIN in $(ls $ORIG_PATH/plugins/$PLUGIN_TYPE/mains); do cd $ORIG_PATH/plugins/$PLUGIN_TYPE/mains/$CURR_PLUGIN; go build -v -o $ORIG_PATH/plugins/$PLUGIN_TYPE/assets/boundary-plugin-${PLUGIN_TYPE}-${CURR_PLUGIN} .; cd $ORIG_PATH; done; cd $ORIG_PATH/plugins/$PLUGIN_TYPE/assets; for CURR_PLUGIN in $(ls); do gzip -9 $CURR_PLUGIN; done; cd $ORIG_PATH; done; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary aliases: - type: local path: boundary_0.6.2_freebsd_386.zip - type: public-hc-releases path: boundary/boundary_0.6.2/boundary_0.6.2_freebsd_386.zip - - packagespecid: 7b4b394a2ac479c3d6d9b0aa24b944a3bb01e292 + - packagespecid: 0101b0c84138c8e37a5affd1171d04237dcd044f inputs: BINARY_NAME: boundary BUILD_TAGS: ui @@ -119,7 +162,7 @@ packages: - type: base name: 00-base-cc87158f920a83b1c241d68474eed6734b32c511 archive: .buildcache/archives/00-base-cc87158f920a83b1c241d68474eed6734b32c511.tar.gz - PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-7b4b394a2ac479c3d6d9b0aa24b944a3bb01e292 + PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-0101b0c84138c8e37a5affd1171d04237dcd044f POST_PROCESSORS: av-scan circleci: BUILDER_CACHE_KEY_PREFIX_LIST: @@ -127,14 +170,14 @@ packages: - go-modules_74c0_{{checksum ".buildcache/cache-keys/go-modules-74c0c94188690e22d738cb1784f8feba7c3dc00d"}} - ui_dfc9_{{checksum ".buildcache/cache-keys/ui-dfc937906b9b645655c44bc782ef13c64ebe8dbb"}} - base_cc87_{{checksum ".buildcache/cache-keys/base-cc87158f920a83b1c241d68474eed6734b32c511"}} - PACKAGE_CACHE_KEY: package-7b4b394a2ac479c3d6d9b0aa24b944a3bb01e292-{{checksum ".buildcache/cache-keys/package-7b4b394a2ac479c3d6d9b0aa24b944a3bb01e292"}} - build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary + PACKAGE_CACHE_KEY: package-0101b0c84138c8e37a5affd1171d04237dcd044f-{{checksum ".buildcache/cache-keys/package-0101b0c84138c8e37a5affd1171d04237dcd044f"}} + build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; ORIG_PATH=$(pwd); for PLUGIN_TYPE in host; do for CURR_PLUGIN in $(ls $ORIG_PATH/plugins/$PLUGIN_TYPE/mains); do cd $ORIG_PATH/plugins/$PLUGIN_TYPE/mains/$CURR_PLUGIN; go build -v -o $ORIG_PATH/plugins/$PLUGIN_TYPE/assets/boundary-plugin-${PLUGIN_TYPE}-${CURR_PLUGIN} .; cd $ORIG_PATH; done; cd $ORIG_PATH/plugins/$PLUGIN_TYPE/assets; for CURR_PLUGIN in $(ls); do gzip -9 $CURR_PLUGIN; done; cd $ORIG_PATH; done; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary aliases: - type: local path: boundary_0.6.2_freebsd_amd64.zip - type: public-hc-releases path: boundary/boundary_0.6.2/boundary_0.6.2_freebsd_amd64.zip - - packagespecid: 6f96cd412f008849de6529e990e5b46f75085f0e + - packagespecid: 3735bcca8df7adfefa7d79ae87bb6731ed5e074f inputs: BINARY_NAME: boundary BUILD_TAGS: ui @@ -162,7 +205,7 @@ packages: - type: base name: 00-base-cc87158f920a83b1c241d68474eed6734b32c511 archive: .buildcache/archives/00-base-cc87158f920a83b1c241d68474eed6734b32c511.tar.gz - PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-6f96cd412f008849de6529e990e5b46f75085f0e + PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-3735bcca8df7adfefa7d79ae87bb6731ed5e074f POST_PROCESSORS: av-scan circleci: BUILDER_CACHE_KEY_PREFIX_LIST: @@ -170,14 +213,14 @@ packages: - go-modules_74c0_{{checksum ".buildcache/cache-keys/go-modules-74c0c94188690e22d738cb1784f8feba7c3dc00d"}} - ui_dfc9_{{checksum ".buildcache/cache-keys/ui-dfc937906b9b645655c44bc782ef13c64ebe8dbb"}} - base_cc87_{{checksum ".buildcache/cache-keys/base-cc87158f920a83b1c241d68474eed6734b32c511"}} - PACKAGE_CACHE_KEY: package-6f96cd412f008849de6529e990e5b46f75085f0e-{{checksum ".buildcache/cache-keys/package-6f96cd412f008849de6529e990e5b46f75085f0e"}} - build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary + PACKAGE_CACHE_KEY: package-3735bcca8df7adfefa7d79ae87bb6731ed5e074f-{{checksum ".buildcache/cache-keys/package-3735bcca8df7adfefa7d79ae87bb6731ed5e074f"}} + build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; ORIG_PATH=$(pwd); for PLUGIN_TYPE in host; do for CURR_PLUGIN in $(ls $ORIG_PATH/plugins/$PLUGIN_TYPE/mains); do cd $ORIG_PATH/plugins/$PLUGIN_TYPE/mains/$CURR_PLUGIN; go build -v -o $ORIG_PATH/plugins/$PLUGIN_TYPE/assets/boundary-plugin-${PLUGIN_TYPE}-${CURR_PLUGIN} .; cd $ORIG_PATH; done; cd $ORIG_PATH/plugins/$PLUGIN_TYPE/assets; for CURR_PLUGIN in $(ls); do gzip -9 $CURR_PLUGIN; done; cd $ORIG_PATH; done; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary aliases: - type: local path: boundary_0.6.2_freebsd_arm.zip - type: public-hc-releases path: boundary/boundary_0.6.2/boundary_0.6.2_freebsd_arm.zip - - packagespecid: 36979f31c13248a027ff8866979f2aa282c9f9d6 + - packagespecid: 81b9112a9e1bd1d33778c4673adbec942c595a7e inputs: BINARY_NAME: boundary BUILD_TAGS: ui @@ -205,7 +248,7 @@ packages: - type: base name: 00-base-cc87158f920a83b1c241d68474eed6734b32c511 archive: .buildcache/archives/00-base-cc87158f920a83b1c241d68474eed6734b32c511.tar.gz - PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-36979f31c13248a027ff8866979f2aa282c9f9d6 + PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-81b9112a9e1bd1d33778c4673adbec942c595a7e POST_PROCESSORS: av-scan circleci: BUILDER_CACHE_KEY_PREFIX_LIST: @@ -213,14 +256,14 @@ packages: - go-modules_74c0_{{checksum ".buildcache/cache-keys/go-modules-74c0c94188690e22d738cb1784f8feba7c3dc00d"}} - ui_dfc9_{{checksum ".buildcache/cache-keys/ui-dfc937906b9b645655c44bc782ef13c64ebe8dbb"}} - base_cc87_{{checksum ".buildcache/cache-keys/base-cc87158f920a83b1c241d68474eed6734b32c511"}} - PACKAGE_CACHE_KEY: package-36979f31c13248a027ff8866979f2aa282c9f9d6-{{checksum ".buildcache/cache-keys/package-36979f31c13248a027ff8866979f2aa282c9f9d6"}} - build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary + PACKAGE_CACHE_KEY: package-81b9112a9e1bd1d33778c4673adbec942c595a7e-{{checksum ".buildcache/cache-keys/package-81b9112a9e1bd1d33778c4673adbec942c595a7e"}} + build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; ORIG_PATH=$(pwd); for PLUGIN_TYPE in host; do for CURR_PLUGIN in $(ls $ORIG_PATH/plugins/$PLUGIN_TYPE/mains); do cd $ORIG_PATH/plugins/$PLUGIN_TYPE/mains/$CURR_PLUGIN; go build -v -o $ORIG_PATH/plugins/$PLUGIN_TYPE/assets/boundary-plugin-${PLUGIN_TYPE}-${CURR_PLUGIN} .; cd $ORIG_PATH; done; cd $ORIG_PATH/plugins/$PLUGIN_TYPE/assets; for CURR_PLUGIN in $(ls); do gzip -9 $CURR_PLUGIN; done; cd $ORIG_PATH; done; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary aliases: - type: local path: boundary_0.6.2_linux_386.zip - type: public-hc-releases path: boundary/boundary_0.6.2/boundary_0.6.2_linux_386.zip - - packagespecid: 219d6c3eddb2481810202dccfc71a8467e4a077a + - packagespecid: b04f52247addbb91ec89a35be1ddff9feadf7090 inputs: BINARY_NAME: boundary BUILD_TAGS: ui @@ -248,7 +291,7 @@ packages: - type: base name: 00-base-cc87158f920a83b1c241d68474eed6734b32c511 archive: .buildcache/archives/00-base-cc87158f920a83b1c241d68474eed6734b32c511.tar.gz - PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-219d6c3eddb2481810202dccfc71a8467e4a077a + PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-b04f52247addbb91ec89a35be1ddff9feadf7090 POST_PROCESSORS: av-scan circleci: BUILDER_CACHE_KEY_PREFIX_LIST: @@ -256,14 +299,14 @@ packages: - go-modules_74c0_{{checksum ".buildcache/cache-keys/go-modules-74c0c94188690e22d738cb1784f8feba7c3dc00d"}} - ui_dfc9_{{checksum ".buildcache/cache-keys/ui-dfc937906b9b645655c44bc782ef13c64ebe8dbb"}} - base_cc87_{{checksum ".buildcache/cache-keys/base-cc87158f920a83b1c241d68474eed6734b32c511"}} - PACKAGE_CACHE_KEY: package-219d6c3eddb2481810202dccfc71a8467e4a077a-{{checksum ".buildcache/cache-keys/package-219d6c3eddb2481810202dccfc71a8467e4a077a"}} - build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary + PACKAGE_CACHE_KEY: package-b04f52247addbb91ec89a35be1ddff9feadf7090-{{checksum ".buildcache/cache-keys/package-b04f52247addbb91ec89a35be1ddff9feadf7090"}} + build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; ORIG_PATH=$(pwd); for PLUGIN_TYPE in host; do for CURR_PLUGIN in $(ls $ORIG_PATH/plugins/$PLUGIN_TYPE/mains); do cd $ORIG_PATH/plugins/$PLUGIN_TYPE/mains/$CURR_PLUGIN; go build -v -o $ORIG_PATH/plugins/$PLUGIN_TYPE/assets/boundary-plugin-${PLUGIN_TYPE}-${CURR_PLUGIN} .; cd $ORIG_PATH; done; cd $ORIG_PATH/plugins/$PLUGIN_TYPE/assets; for CURR_PLUGIN in $(ls); do gzip -9 $CURR_PLUGIN; done; cd $ORIG_PATH; done; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary aliases: - type: local path: boundary_0.6.2_linux_amd64.zip - type: public-hc-releases path: boundary/boundary_0.6.2/boundary_0.6.2_linux_amd64.zip - - packagespecid: 1a1ebfab53158923bc4779535ebd667ff8ed5ca3 + - packagespecid: 25033317f5db33c76740dba3e792063240c317b1 inputs: BINARY_NAME: boundary BUILD_TAGS: ui @@ -291,7 +334,7 @@ packages: - type: base name: 00-base-cc87158f920a83b1c241d68474eed6734b32c511 archive: .buildcache/archives/00-base-cc87158f920a83b1c241d68474eed6734b32c511.tar.gz - PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-1a1ebfab53158923bc4779535ebd667ff8ed5ca3 + PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-25033317f5db33c76740dba3e792063240c317b1 POST_PROCESSORS: av-scan circleci: BUILDER_CACHE_KEY_PREFIX_LIST: @@ -299,14 +342,14 @@ packages: - go-modules_74c0_{{checksum ".buildcache/cache-keys/go-modules-74c0c94188690e22d738cb1784f8feba7c3dc00d"}} - ui_dfc9_{{checksum ".buildcache/cache-keys/ui-dfc937906b9b645655c44bc782ef13c64ebe8dbb"}} - base_cc87_{{checksum ".buildcache/cache-keys/base-cc87158f920a83b1c241d68474eed6734b32c511"}} - PACKAGE_CACHE_KEY: package-1a1ebfab53158923bc4779535ebd667ff8ed5ca3-{{checksum ".buildcache/cache-keys/package-1a1ebfab53158923bc4779535ebd667ff8ed5ca3"}} - build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary + PACKAGE_CACHE_KEY: package-25033317f5db33c76740dba3e792063240c317b1-{{checksum ".buildcache/cache-keys/package-25033317f5db33c76740dba3e792063240c317b1"}} + build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; ORIG_PATH=$(pwd); for PLUGIN_TYPE in host; do for CURR_PLUGIN in $(ls $ORIG_PATH/plugins/$PLUGIN_TYPE/mains); do cd $ORIG_PATH/plugins/$PLUGIN_TYPE/mains/$CURR_PLUGIN; go build -v -o $ORIG_PATH/plugins/$PLUGIN_TYPE/assets/boundary-plugin-${PLUGIN_TYPE}-${CURR_PLUGIN} .; cd $ORIG_PATH; done; cd $ORIG_PATH/plugins/$PLUGIN_TYPE/assets; for CURR_PLUGIN in $(ls); do gzip -9 $CURR_PLUGIN; done; cd $ORIG_PATH; done; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary aliases: - type: local path: boundary_0.6.2_linux_arm.zip - type: public-hc-releases path: boundary/boundary_0.6.2/boundary_0.6.2_linux_arm.zip - - packagespecid: 100f99d19a296ddc4ba797c583b4c68266d61c52 + - packagespecid: dc42bacf89f0d78468257a40c404e141fa9272c8 inputs: BINARY_NAME: boundary BUILD_TAGS: ui @@ -334,7 +377,7 @@ packages: - type: base name: 00-base-cc87158f920a83b1c241d68474eed6734b32c511 archive: .buildcache/archives/00-base-cc87158f920a83b1c241d68474eed6734b32c511.tar.gz - PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-100f99d19a296ddc4ba797c583b4c68266d61c52 + PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-dc42bacf89f0d78468257a40c404e141fa9272c8 POST_PROCESSORS: av-scan circleci: BUILDER_CACHE_KEY_PREFIX_LIST: @@ -342,14 +385,14 @@ packages: - go-modules_74c0_{{checksum ".buildcache/cache-keys/go-modules-74c0c94188690e22d738cb1784f8feba7c3dc00d"}} - ui_dfc9_{{checksum ".buildcache/cache-keys/ui-dfc937906b9b645655c44bc782ef13c64ebe8dbb"}} - base_cc87_{{checksum ".buildcache/cache-keys/base-cc87158f920a83b1c241d68474eed6734b32c511"}} - PACKAGE_CACHE_KEY: package-100f99d19a296ddc4ba797c583b4c68266d61c52-{{checksum ".buildcache/cache-keys/package-100f99d19a296ddc4ba797c583b4c68266d61c52"}} - build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary + PACKAGE_CACHE_KEY: package-dc42bacf89f0d78468257a40c404e141fa9272c8-{{checksum ".buildcache/cache-keys/package-dc42bacf89f0d78468257a40c404e141fa9272c8"}} + build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; ORIG_PATH=$(pwd); for PLUGIN_TYPE in host; do for CURR_PLUGIN in $(ls $ORIG_PATH/plugins/$PLUGIN_TYPE/mains); do cd $ORIG_PATH/plugins/$PLUGIN_TYPE/mains/$CURR_PLUGIN; go build -v -o $ORIG_PATH/plugins/$PLUGIN_TYPE/assets/boundary-plugin-${PLUGIN_TYPE}-${CURR_PLUGIN} .; cd $ORIG_PATH; done; cd $ORIG_PATH/plugins/$PLUGIN_TYPE/assets; for CURR_PLUGIN in $(ls); do gzip -9 $CURR_PLUGIN; done; cd $ORIG_PATH; done; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary aliases: - type: local path: boundary_0.6.2_linux_arm64.zip - type: public-hc-releases path: boundary/boundary_0.6.2/boundary_0.6.2_linux_arm64.zip - - packagespecid: 0ab42b4e27fc1be0012f56be6566d1f79416efc4 + - packagespecid: f56a0ca98b1bf7e4f647cedee7db20d8b940a1e0 inputs: BINARY_NAME: boundary BUILD_TAGS: ui @@ -377,7 +420,7 @@ packages: - type: base name: 00-base-cc87158f920a83b1c241d68474eed6734b32c511 archive: .buildcache/archives/00-base-cc87158f920a83b1c241d68474eed6734b32c511.tar.gz - PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-0ab42b4e27fc1be0012f56be6566d1f79416efc4 + PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-f56a0ca98b1bf7e4f647cedee7db20d8b940a1e0 POST_PROCESSORS: av-scan circleci: BUILDER_CACHE_KEY_PREFIX_LIST: @@ -385,14 +428,14 @@ packages: - go-modules_74c0_{{checksum ".buildcache/cache-keys/go-modules-74c0c94188690e22d738cb1784f8feba7c3dc00d"}} - ui_dfc9_{{checksum ".buildcache/cache-keys/ui-dfc937906b9b645655c44bc782ef13c64ebe8dbb"}} - base_cc87_{{checksum ".buildcache/cache-keys/base-cc87158f920a83b1c241d68474eed6734b32c511"}} - PACKAGE_CACHE_KEY: package-0ab42b4e27fc1be0012f56be6566d1f79416efc4-{{checksum ".buildcache/cache-keys/package-0ab42b4e27fc1be0012f56be6566d1f79416efc4"}} - build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary + PACKAGE_CACHE_KEY: package-f56a0ca98b1bf7e4f647cedee7db20d8b940a1e0-{{checksum ".buildcache/cache-keys/package-f56a0ca98b1bf7e4f647cedee7db20d8b940a1e0"}} + build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; ORIG_PATH=$(pwd); for PLUGIN_TYPE in host; do for CURR_PLUGIN in $(ls $ORIG_PATH/plugins/$PLUGIN_TYPE/mains); do cd $ORIG_PATH/plugins/$PLUGIN_TYPE/mains/$CURR_PLUGIN; go build -v -o $ORIG_PATH/plugins/$PLUGIN_TYPE/assets/boundary-plugin-${PLUGIN_TYPE}-${CURR_PLUGIN} .; cd $ORIG_PATH; done; cd $ORIG_PATH/plugins/$PLUGIN_TYPE/assets; for CURR_PLUGIN in $(ls); do gzip -9 $CURR_PLUGIN; done; cd $ORIG_PATH; done; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary aliases: - type: local path: boundary_0.6.2_netbsd_386.zip - type: public-hc-releases path: boundary/boundary_0.6.2/boundary_0.6.2_netbsd_386.zip - - packagespecid: f6ec6dce632eda1d6b6fc812702ddbfc78b00209 + - packagespecid: 8dd295cf186df50e92ed66575b73e97003885428 inputs: BINARY_NAME: boundary BUILD_TAGS: ui @@ -420,7 +463,7 @@ packages: - type: base name: 00-base-cc87158f920a83b1c241d68474eed6734b32c511 archive: .buildcache/archives/00-base-cc87158f920a83b1c241d68474eed6734b32c511.tar.gz - PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-f6ec6dce632eda1d6b6fc812702ddbfc78b00209 + PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-8dd295cf186df50e92ed66575b73e97003885428 POST_PROCESSORS: av-scan circleci: BUILDER_CACHE_KEY_PREFIX_LIST: @@ -428,14 +471,14 @@ packages: - go-modules_74c0_{{checksum ".buildcache/cache-keys/go-modules-74c0c94188690e22d738cb1784f8feba7c3dc00d"}} - ui_dfc9_{{checksum ".buildcache/cache-keys/ui-dfc937906b9b645655c44bc782ef13c64ebe8dbb"}} - base_cc87_{{checksum ".buildcache/cache-keys/base-cc87158f920a83b1c241d68474eed6734b32c511"}} - PACKAGE_CACHE_KEY: package-f6ec6dce632eda1d6b6fc812702ddbfc78b00209-{{checksum ".buildcache/cache-keys/package-f6ec6dce632eda1d6b6fc812702ddbfc78b00209"}} - build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary + PACKAGE_CACHE_KEY: package-8dd295cf186df50e92ed66575b73e97003885428-{{checksum ".buildcache/cache-keys/package-8dd295cf186df50e92ed66575b73e97003885428"}} + build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; ORIG_PATH=$(pwd); for PLUGIN_TYPE in host; do for CURR_PLUGIN in $(ls $ORIG_PATH/plugins/$PLUGIN_TYPE/mains); do cd $ORIG_PATH/plugins/$PLUGIN_TYPE/mains/$CURR_PLUGIN; go build -v -o $ORIG_PATH/plugins/$PLUGIN_TYPE/assets/boundary-plugin-${PLUGIN_TYPE}-${CURR_PLUGIN} .; cd $ORIG_PATH; done; cd $ORIG_PATH/plugins/$PLUGIN_TYPE/assets; for CURR_PLUGIN in $(ls); do gzip -9 $CURR_PLUGIN; done; cd $ORIG_PATH; done; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary aliases: - type: local path: boundary_0.6.2_netbsd_amd64.zip - type: public-hc-releases path: boundary/boundary_0.6.2/boundary_0.6.2_netbsd_amd64.zip - - packagespecid: db956b3de630063891714da55d71c00aee11a868 + - packagespecid: 997f9a9e448c02fe17505a5b75635b9dd1104d92 inputs: BINARY_NAME: boundary BUILD_TAGS: ui @@ -463,7 +506,7 @@ packages: - type: base name: 00-base-cc87158f920a83b1c241d68474eed6734b32c511 archive: .buildcache/archives/00-base-cc87158f920a83b1c241d68474eed6734b32c511.tar.gz - PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-db956b3de630063891714da55d71c00aee11a868 + PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-997f9a9e448c02fe17505a5b75635b9dd1104d92 POST_PROCESSORS: av-scan circleci: BUILDER_CACHE_KEY_PREFIX_LIST: @@ -471,14 +514,14 @@ packages: - go-modules_74c0_{{checksum ".buildcache/cache-keys/go-modules-74c0c94188690e22d738cb1784f8feba7c3dc00d"}} - ui_dfc9_{{checksum ".buildcache/cache-keys/ui-dfc937906b9b645655c44bc782ef13c64ebe8dbb"}} - base_cc87_{{checksum ".buildcache/cache-keys/base-cc87158f920a83b1c241d68474eed6734b32c511"}} - PACKAGE_CACHE_KEY: package-db956b3de630063891714da55d71c00aee11a868-{{checksum ".buildcache/cache-keys/package-db956b3de630063891714da55d71c00aee11a868"}} - build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary + PACKAGE_CACHE_KEY: package-997f9a9e448c02fe17505a5b75635b9dd1104d92-{{checksum ".buildcache/cache-keys/package-997f9a9e448c02fe17505a5b75635b9dd1104d92"}} + build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; ORIG_PATH=$(pwd); for PLUGIN_TYPE in host; do for CURR_PLUGIN in $(ls $ORIG_PATH/plugins/$PLUGIN_TYPE/mains); do cd $ORIG_PATH/plugins/$PLUGIN_TYPE/mains/$CURR_PLUGIN; go build -v -o $ORIG_PATH/plugins/$PLUGIN_TYPE/assets/boundary-plugin-${PLUGIN_TYPE}-${CURR_PLUGIN} .; cd $ORIG_PATH; done; cd $ORIG_PATH/plugins/$PLUGIN_TYPE/assets; for CURR_PLUGIN in $(ls); do gzip -9 $CURR_PLUGIN; done; cd $ORIG_PATH; done; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary aliases: - type: local path: boundary_0.6.2_openbsd_386.zip - type: public-hc-releases path: boundary/boundary_0.6.2/boundary_0.6.2_openbsd_386.zip - - packagespecid: 1c616d887a6ba6f72bf57aaae02b62cfe2260056 + - packagespecid: 505ab0aca6b881919f05bb8e0125783a5799b4fc inputs: BINARY_NAME: boundary BUILD_TAGS: ui @@ -506,7 +549,7 @@ packages: - type: base name: 00-base-cc87158f920a83b1c241d68474eed6734b32c511 archive: .buildcache/archives/00-base-cc87158f920a83b1c241d68474eed6734b32c511.tar.gz - PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-1c616d887a6ba6f72bf57aaae02b62cfe2260056 + PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-505ab0aca6b881919f05bb8e0125783a5799b4fc POST_PROCESSORS: av-scan circleci: BUILDER_CACHE_KEY_PREFIX_LIST: @@ -514,14 +557,14 @@ packages: - go-modules_74c0_{{checksum ".buildcache/cache-keys/go-modules-74c0c94188690e22d738cb1784f8feba7c3dc00d"}} - ui_dfc9_{{checksum ".buildcache/cache-keys/ui-dfc937906b9b645655c44bc782ef13c64ebe8dbb"}} - base_cc87_{{checksum ".buildcache/cache-keys/base-cc87158f920a83b1c241d68474eed6734b32c511"}} - PACKAGE_CACHE_KEY: package-1c616d887a6ba6f72bf57aaae02b62cfe2260056-{{checksum ".buildcache/cache-keys/package-1c616d887a6ba6f72bf57aaae02b62cfe2260056"}} - build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary + PACKAGE_CACHE_KEY: package-505ab0aca6b881919f05bb8e0125783a5799b4fc-{{checksum ".buildcache/cache-keys/package-505ab0aca6b881919f05bb8e0125783a5799b4fc"}} + build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; ORIG_PATH=$(pwd); for PLUGIN_TYPE in host; do for CURR_PLUGIN in $(ls $ORIG_PATH/plugins/$PLUGIN_TYPE/mains); do cd $ORIG_PATH/plugins/$PLUGIN_TYPE/mains/$CURR_PLUGIN; go build -v -o $ORIG_PATH/plugins/$PLUGIN_TYPE/assets/boundary-plugin-${PLUGIN_TYPE}-${CURR_PLUGIN} .; cd $ORIG_PATH; done; cd $ORIG_PATH/plugins/$PLUGIN_TYPE/assets; for CURR_PLUGIN in $(ls); do gzip -9 $CURR_PLUGIN; done; cd $ORIG_PATH; done; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary aliases: - type: local path: boundary_0.6.2_openbsd_amd64.zip - type: public-hc-releases path: boundary/boundary_0.6.2/boundary_0.6.2_openbsd_amd64.zip - - packagespecid: 02bf7eae9ab7517d616b442655b0db880a355a5e + - packagespecid: b7d03bef8d16c702d595798df0cbda643915292c inputs: BINARY_NAME: boundary BUILD_TAGS: ui @@ -549,7 +592,7 @@ packages: - type: base name: 00-base-cc87158f920a83b1c241d68474eed6734b32c511 archive: .buildcache/archives/00-base-cc87158f920a83b1c241d68474eed6734b32c511.tar.gz - PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-02bf7eae9ab7517d616b442655b0db880a355a5e + PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-b7d03bef8d16c702d595798df0cbda643915292c POST_PROCESSORS: av-scan circleci: BUILDER_CACHE_KEY_PREFIX_LIST: @@ -557,14 +600,14 @@ packages: - go-modules_74c0_{{checksum ".buildcache/cache-keys/go-modules-74c0c94188690e22d738cb1784f8feba7c3dc00d"}} - ui_dfc9_{{checksum ".buildcache/cache-keys/ui-dfc937906b9b645655c44bc782ef13c64ebe8dbb"}} - base_cc87_{{checksum ".buildcache/cache-keys/base-cc87158f920a83b1c241d68474eed6734b32c511"}} - PACKAGE_CACHE_KEY: package-02bf7eae9ab7517d616b442655b0db880a355a5e-{{checksum ".buildcache/cache-keys/package-02bf7eae9ab7517d616b442655b0db880a355a5e"}} - build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary + PACKAGE_CACHE_KEY: package-b7d03bef8d16c702d595798df0cbda643915292c-{{checksum ".buildcache/cache-keys/package-b7d03bef8d16c702d595798df0cbda643915292c"}} + build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; ORIG_PATH=$(pwd); for PLUGIN_TYPE in host; do for CURR_PLUGIN in $(ls $ORIG_PATH/plugins/$PLUGIN_TYPE/mains); do cd $ORIG_PATH/plugins/$PLUGIN_TYPE/mains/$CURR_PLUGIN; go build -v -o $ORIG_PATH/plugins/$PLUGIN_TYPE/assets/boundary-plugin-${PLUGIN_TYPE}-${CURR_PLUGIN} .; cd $ORIG_PATH; done; cd $ORIG_PATH/plugins/$PLUGIN_TYPE/assets; for CURR_PLUGIN in $(ls); do gzip -9 $CURR_PLUGIN; done; cd $ORIG_PATH; done; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary aliases: - type: local path: boundary_0.6.2_solaris_amd64.zip - type: public-hc-releases path: boundary/boundary_0.6.2/boundary_0.6.2_solaris_amd64.zip - - packagespecid: e57134ec78767637844ee933e9c9a58bd3a24182 + - packagespecid: a68848e3db3953d016f73aabb3d732a0333b7bc9 inputs: BINARY_NAME: boundary.exe BUILD_TAGS: ui @@ -592,7 +635,7 @@ packages: - type: base name: 00-base-cc87158f920a83b1c241d68474eed6734b32c511 archive: .buildcache/archives/00-base-cc87158f920a83b1c241d68474eed6734b32c511.tar.gz - PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-e57134ec78767637844ee933e9c9a58bd3a24182 + PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-a68848e3db3953d016f73aabb3d732a0333b7bc9 POST_PROCESSORS: microsoft-notarize av-scan circleci: BUILDER_CACHE_KEY_PREFIX_LIST: @@ -600,14 +643,14 @@ packages: - go-modules_74c0_{{checksum ".buildcache/cache-keys/go-modules-74c0c94188690e22d738cb1784f8feba7c3dc00d"}} - ui_dfc9_{{checksum ".buildcache/cache-keys/ui-dfc937906b9b645655c44bc782ef13c64ebe8dbb"}} - base_cc87_{{checksum ".buildcache/cache-keys/base-cc87158f920a83b1c241d68474eed6734b32c511"}} - PACKAGE_CACHE_KEY: package-e57134ec78767637844ee933e9c9a58bd3a24182-{{checksum ".buildcache/cache-keys/package-e57134ec78767637844ee933e9c9a58bd3a24182"}} - build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary.exe ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary.exe + PACKAGE_CACHE_KEY: package-a68848e3db3953d016f73aabb3d732a0333b7bc9-{{checksum ".buildcache/cache-keys/package-a68848e3db3953d016f73aabb3d732a0333b7bc9"}} + build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; ORIG_PATH=$(pwd); for PLUGIN_TYPE in host; do for CURR_PLUGIN in $(ls $ORIG_PATH/plugins/$PLUGIN_TYPE/mains); do cd $ORIG_PATH/plugins/$PLUGIN_TYPE/mains/$CURR_PLUGIN; go build -v -o $ORIG_PATH/plugins/$PLUGIN_TYPE/assets/boundary-plugin-${PLUGIN_TYPE}-${CURR_PLUGIN} .; cd $ORIG_PATH; done; cd $ORIG_PATH/plugins/$PLUGIN_TYPE/assets; for CURR_PLUGIN in $(ls); do gzip -9 $CURR_PLUGIN; done; cd $ORIG_PATH; done; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary.exe ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary.exe aliases: - type: local path: boundary_0.6.2_windows_386.zip - type: public-hc-releases path: boundary/boundary_0.6.2/boundary_0.6.2_windows_386.zip - - packagespecid: 60c90459d2d4978488819b2ca4118ba602308095 + - packagespecid: 89694dc28c6ddc1d0648b69cefdcc9f67d59a240 inputs: BINARY_NAME: boundary.exe BUILD_TAGS: ui @@ -635,7 +678,7 @@ packages: - type: base name: 00-base-cc87158f920a83b1c241d68474eed6734b32c511 archive: .buildcache/archives/00-base-cc87158f920a83b1c241d68474eed6734b32c511.tar.gz - PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-60c90459d2d4978488819b2ca4118ba602308095 + PACKAGE_CACHE_KEY_FILE: .buildcache/cache-keys/package-89694dc28c6ddc1d0648b69cefdcc9f67d59a240 POST_PROCESSORS: microsoft-notarize av-scan circleci: BUILDER_CACHE_KEY_PREFIX_LIST: @@ -643,8 +686,8 @@ packages: - go-modules_74c0_{{checksum ".buildcache/cache-keys/go-modules-74c0c94188690e22d738cb1784f8feba7c3dc00d"}} - ui_dfc9_{{checksum ".buildcache/cache-keys/ui-dfc937906b9b645655c44bc782ef13c64ebe8dbb"}} - base_cc87_{{checksum ".buildcache/cache-keys/base-cc87158f920a83b1c241d68474eed6734b32c511"}} - PACKAGE_CACHE_KEY: package-60c90459d2d4978488819b2ca4118ba602308095-{{checksum ".buildcache/cache-keys/package-60c90459d2d4978488819b2ca4118ba602308095"}} - build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary.exe ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary.exe + PACKAGE_CACHE_KEY: package-89694dc28c6ddc1d0648b69cefdcc9f67d59a240-{{checksum ".buildcache/cache-keys/package-89694dc28c6ddc1d0648b69cefdcc9f67d59a240"}} + build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; ORIG_PATH=$(pwd); for PLUGIN_TYPE in host; do for CURR_PLUGIN in $(ls $ORIG_PATH/plugins/$PLUGIN_TYPE/mains); do cd $ORIG_PATH/plugins/$PLUGIN_TYPE/mains/$CURR_PLUGIN; go build -v -o $ORIG_PATH/plugins/$PLUGIN_TYPE/assets/boundary-plugin-${PLUGIN_TYPE}-${CURR_PLUGIN} .; cd $ORIG_PATH; done; cd $ORIG_PATH/plugins/$PLUGIN_TYPE/assets; for CURR_PLUGIN in $(ls); do gzip -9 $CURR_PLUGIN; done; cd $ORIG_PATH; done; go build -v -tags 'ui' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version=0.6.2 -X $VERSION_PKG_PATH.VersionPrerelease=" -o $OUTPUT_DIR/boundary.exe ./cmd/boundary && cd $OUTPUT_DIR && zip $PACKAGE_ZIP_NAME boundary.exe aliases: - type: local path: boundary_0.6.2_windows_amd64.zip diff --git a/packages-oss.yml b/packages-oss.yml index 80d46a00d..b2b924c1a 100644 --- a/packages-oss.yml +++ b/packages-oss.yml @@ -21,6 +21,7 @@ inputs: packages: - inputs: { GOOS: darwin, GOARCH: amd64 } + - inputs: { GOOS: darwin, GOARCH: arm64 } - inputs: { GOOS: freebsd, GOARCH: 386 } - inputs: { GOOS: freebsd, GOARCH: amd64 } - inputs: { GOOS: freebsd, GOARCH: arm } @@ -96,6 +97,19 @@ layers: build-command: VERSION_PKG_PATH=github.com/hashicorp/boundary/version; unset GOPATH; + ORIG_PATH=$(pwd); + for PLUGIN_TYPE in host; do + for CURR_PLUGIN in $(ls $ORIG_PATH/plugins/$PLUGIN_TYPE/mains); do + cd $ORIG_PATH/plugins/$PLUGIN_TYPE/mains/$CURR_PLUGIN; + go build -v -o $ORIG_PATH/plugins/$PLUGIN_TYPE/assets/boundary-plugin-${PLUGIN_TYPE}-${CURR_PLUGIN} .; + cd $ORIG_PATH; + done; + cd $ORIG_PATH/plugins/$PLUGIN_TYPE/assets; + for CURR_PLUGIN in $(ls); do + gzip -9 $CURR_PLUGIN; + done; + cd $ORIG_PATH; + done; go build -v -tags '{{.BUILD_TAGS}}' -ldflags "-X $VERSION_PKG_PATH.GitCommit=$PACKAGE_SOURCE_ID -X $VERSION_PKG_PATH.Version={{.PRODUCT_VERSION_MMP}} diff --git a/plugins/README.md b/plugins/README.md new file mode 100644 index 000000000..64f20d00f --- /dev/null +++ b/plugins/README.md @@ -0,0 +1,6 @@ +# Boundary External Plugins Modules + +The Go modules under `plugins` are not intended to be used directly. They are +here purely to allow compilation of the various plugins Boundary supports +without pulling the dependencies (and any clashes, and any init behavior) +directly into Boundary. diff --git a/plugins/host/assets.go b/plugins/host/assets.go new file mode 100644 index 000000000..83447e592 --- /dev/null +++ b/plugins/host/assets.go @@ -0,0 +1,21 @@ +package host_plugin_assets + +import ( + "embed" + "io/fs" +) + +const contentDir = "assets" + +// content is our static web server content. +//go:embed assets +var content embed.FS + +func FileSystem() fs.FS { + // Remove the root + f, err := fs.Sub(content, contentDir) + if err != nil { + panic(err) + } + return f +} diff --git a/plugins/host/mains/aws/go.mod b/plugins/host/mains/aws/go.mod new file mode 100644 index 000000000..2b14f5d14 --- /dev/null +++ b/plugins/host/mains/aws/go.mod @@ -0,0 +1,53 @@ +module github.com/hashicorp/boundary/plugins/host/mains/aws + +go 1.17 + +require ( + github.com/hashicorp/boundary-plugin-host-aws v0.0.0-20211110224123-563a2b99315c + github.com/hashicorp/boundary/sdk v0.0.12-0.20211110201157-962c9463fdb3 + github.com/hashicorp/go-hclog v1.0.0 +) + +require ( + github.com/Azure/azure-sdk-for-go v57.4.0+incompatible // indirect + github.com/Azure/go-autorest/autorest v0.11.21 // indirect + github.com/Azure/go-autorest/autorest/azure/auth v0.5.8 // indirect + github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect + github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect + github.com/aws/aws-sdk-go v1.42.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fatih/color v1.13.0 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/eventlogger v0.1.1-0.20211104100552-e1e801e50144 // indirect + github.com/hashicorp/eventlogger/filters/encrypt v0.1.6-0.20211027211326-5db60a48f239 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-kms-wrapping v0.6.6 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-plugin v1.4.3 // indirect + github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6 // indirect + github.com/hashicorp/go-uuid v1.0.2 // indirect + github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/mattn/go-colorable v0.1.11 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-testing-interface v1.0.0 // indirect + github.com/mitchellh/mapstructure v1.4.2 // indirect + github.com/mitchellh/pointerstructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/oklog/run v1.0.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.7.0 // indirect + golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect + golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect + golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c // indirect + golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef // indirect + golang.org/x/text v0.3.6 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83 // indirect + google.golang.org/grpc v1.40.0 // indirect + google.golang.org/protobuf v1.27.1 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/plugins/host/mains/aws/go.sum b/plugins/host/mains/aws/go.sum new file mode 100644 index 000000000..8c2733eea --- /dev/null +++ b/plugins/host/mains/aws/go.sum @@ -0,0 +1,898 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/azure-sdk-for-go v36.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v57.4.0+incompatible h1:uzhy/mTwu4rq6Be/fJJnE82PRx2A2vjGjzsA3aQ6Qu0= +github.com/Azure/azure-sdk-for-go v57.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0= +github.com/Azure/go-autorest/autorest v0.10.1/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= +github.com/Azure/go-autorest/autorest v0.11.17/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= +github.com/Azure/go-autorest/autorest v0.11.21 h1:w77zY/9RnUAWcIQyDC0Fc89mCvwftR8F+zsR/OH6enk= +github.com/Azure/go-autorest/autorest v0.11.21/go.mod h1:Do/yuMSW/13ayUkcVREpsMHGG+MvV81uzSCFgYPj4tM= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= +github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/adal v0.9.11/go.mod h1:nBKAnTomx8gDtl+3ZCJv2v0KACFHWTB2drffI1B68Pk= +github.com/Azure/go-autorest/autorest/adal v0.9.14 h1:G8hexQdV5D4khOXrWG2YuLCFKhWYmWD8bHYaXN5ophk= +github.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM= +github.com/Azure/go-autorest/autorest/azure/auth v0.5.8 h1:TzPg6B6fTZ0G1zBf3T54aI7p3cAT6u//TOXGPmFMOXg= +github.com/Azure/go-autorest/autorest/azure/auth v0.5.8/go.mod h1:kxyKZTSfKh8OVFWPAgOgQ/frrJgeYQJPyR5fLFmXko4= +github.com/Azure/go-autorest/autorest/azure/cli v0.3.1/go.mod h1:ZG5p860J94/0kI9mNJVoIoLgXcirM2gF5i2kWloofxw= +github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 h1:dMOmEJfkLKW/7JsokJqkyoYSgmR08hi9KrhjZb+JALY= +github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= +github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= +github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= +github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI= +github.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac= +github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= +github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= +github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190620160927-9418d7b0cd0f h1:oRD16bhpKNAanfcDDVU+J0NXqsgHIvGbbe/sy+r6Rs0= +github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190620160927-9418d7b0cd0f/go.mod h1:myCDvQSzCW+wB1WAlocEru4wMGJxy+vlxHdhegi1CDQ= +github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs= +github.com/armon/go-metrics v0.3.3/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.30.27/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go v1.42.2 h1:SXA+B3DT4N3+wJw5X4Jz9/PazkQZQ7k1nXLGZRdFbO4= +github.com/aws/aws-sdk-go v1.42.2/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= +github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= +github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= +github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= +github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200709052629-daa8e1ccc0bc/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= +github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= +github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= +github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v1.4.2-0.20200319182547-c7ad2b866182/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-ldap/ldap/v3 v3.1.3/go.mod h1:3rbOH3jRS2u6jg2rJnKAMLE/xQyCKIveG2Sa/Cohzb8= +github.com/go-ldap/ldap/v3 v3.1.10/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= +github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/boundary-plugin-host-aws v0.0.0-20211110224123-563a2b99315c h1:PWYD0d5D6RNbBzI3iQfUsGSg8hJEH7qe0kfozPAZ7L4= +github.com/hashicorp/boundary-plugin-host-aws v0.0.0-20211110224123-563a2b99315c/go.mod h1:4tHcCu6L2jtMU7/b+K7QAG6R2d26wHBBuu+lPLVJRio= +github.com/hashicorp/boundary/sdk v0.0.12-0.20211110201157-962c9463fdb3 h1:hl/QIALmFBG8bewvH6s/iB4H1kAXOT3DYreX9JTULIs= +github.com/hashicorp/boundary/sdk v0.0.12-0.20211110201157-962c9463fdb3/go.mod h1:gJoqOSNZfoclvLvBqAnfarha9YbZCtuRiYpu64GWX1Q= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/eventlogger v0.1.0/go.mod h1:a3IXf1aEJfpCPzseTOrwKj4fVW/Qn3oEmpQeaIznzH0= +github.com/hashicorp/eventlogger v0.1.1-0.20211104100552-e1e801e50144 h1:PCl0HtlVnIIloIozAKvjICu6K4IghKXAKNny3R3b2nI= +github.com/hashicorp/eventlogger v0.1.1-0.20211104100552-e1e801e50144/go.mod h1:a3IXf1aEJfpCPzseTOrwKj4fVW/Qn3oEmpQeaIznzH0= +github.com/hashicorp/eventlogger/filters/encrypt v0.1.6-0.20211027211326-5db60a48f239 h1:Yh9tY0lige+y0trmjQeT9NRDo6+YvtNAzbmUNOsIUzI= +github.com/hashicorp/eventlogger/filters/encrypt v0.1.6-0.20211027211326-5db60a48f239/go.mod h1:8rcez7Kw1zanB0/074qnOuGu7zxmNh9Xr2ZI+K4xVIA= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.16.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.0.0 h1:bkKf0BeBXcSYa7f5Fyi9gMuQ8gNsxeiNpZjR6VxNZeo= +github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-kms-wrapping v0.6.4/go.mod h1:pRK4UPSmNLEAjLE2qVz7nvS4NTZyyypQCn2G4q7vkps= +github.com/hashicorp/go-kms-wrapping v0.6.5/go.mod h1:9aLqFi2fqK1WMCyTe9bVjB0X6XxCxD+oINL1ovxpgJE= +github.com/hashicorp/go-kms-wrapping v0.6.6 h1:S2lw/8CFAw5kcztPD9iABG8ekjOs5san9C2oZrY6Syw= +github.com/hashicorp/go-kms-wrapping v0.6.6/go.mod h1:9aLqFi2fqK1WMCyTe9bVjB0X6XxCxD+oINL1ovxpgJE= +github.com/hashicorp/go-kms-wrapping/entropy v0.1.0/go.mod h1:d1g9WGtAunDNpek8jUIEJnBlbgKS1N2Q61QkHiZyR1g= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= +github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM= +github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-retryablehttp v0.6.2/go.mod h1:gEx6HMUGxYYhJScX7W1Il64m6cc2C1mDaW3NQ9sY1FY= +github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-retryablehttp v0.7.0 h1:eu1EI/mbirUgP5C8hVsTNaGZreBDlYiwC1FZWkvQPQ4= +github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-secure-stdlib/awsutil v0.1.1/go.mod h1:QRJZ7siKie+SZJB9jLbfKrs0Gd0yPWMtbneg0iU1PrY= +github.com/hashicorp/go-secure-stdlib/awsutil v0.1.2/go.mod h1:QRJZ7siKie+SZJB9jLbfKrs0Gd0yPWMtbneg0iU1PrY= +github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6 h1:W9WN8p6moV1fjKLkeqEgkAMu5rauy9QeYDAmIaPuuiA= +github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6/go.mod h1:MpCPSPGLDILGb4JMm94/mMi3YysIqsXzGCzkEZjcjXg= +github.com/hashicorp/go-secure-stdlib/base62 v0.1.1/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw= +github.com/hashicorp/go-secure-stdlib/configutil v0.1.2 h1:ai0P91rxlyGWkYUH/zFr7mduW2Q+2FMrXawkLM8e7NU= +github.com/hashicorp/go-secure-stdlib/configutil v0.1.2/go.mod h1:EN1DJMjv9y5e/uRAuP2WPibAox5KHEiq2BjZl00aNPQ= +github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.1 h1:HlTofNQN49DTzPCMVDppp+AVxMNVWaHUElpUYC0TRRE= +github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.1/go.mod h1:irE9ILwhE8VooWdhHhBy0qXzOMRlHKoWoMlr31mAFCE= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1 h1:78ki3QBevHwYrVxnyVeaEz+7WtifHhauYF23es/0KlI= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= +github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1 h1:SMGUnbpAcat8rIKHkBPjfv81yC46a8eCNZ2hsR2l1EI= +github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1/go.mod h1:Ch/bf00Qnx77MZd49JRgHYqHQjtEmTgGU2faufpVZb0= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.1 h1:nd0HIW15E6FG1MsnArYaHfuw9C2zgzM8LxkG5Ty/788= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= +github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.1 h1:Yc026VyMyIpq1UWRnakHRG01U8fJm+nEfEmjoAb00n8= +github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.1/go.mod h1:l8slYwnJA26yBz+ErHpp2IRCLr0vuOMGBORIz4rRiAs= +github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw= +github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/terraform-json v0.13.0 h1:Li9L+lKD1FO5RVFRM1mMMIBDoUHslOniyEi5CM+FWGY= +github.com/hashicorp/terraform-json v0.13.0/go.mod h1:y5OdLBCT+rxbwnpxZs9kGL7R9ExU76+cpdY8zHwoazk= +github.com/hashicorp/vault/api v1.0.5-0.20200519221902-385fac77e20f/go.mod h1:euTFbi2YJgwcju3imEt919lhJKF68nN1cQPq3aA+kBE= +github.com/hashicorp/vault/api v1.1.1 h1:907ld+Z9cALyvbZK2qUX9cLwvSaEQsMVQB3x2KE8+AI= +github.com/hashicorp/vault/api v1.1.1/go.mod h1:29UXcn/1cLOPHQNMWA7bCz2By4PSd0VKPAydKXS5yN0= +github.com/hashicorp/vault/sdk v0.1.14-0.20200519221530-14615acda45f/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10= +github.com/hashicorp/vault/sdk v0.2.1 h1:S4O6Iv/dyKlE9AUTXGa7VOvZmsCvg36toPKgV4f2P4M= +github.com/hashicorp/vault/sdk v0.2.1/go.mod h1:WfUiO1vYzfBkz1TmoE4ZGU7HD0T0Cl/rZwaxjBkgN4U= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huaweicloud/golangsdk v0.0.0-20200304081349-45ec0797f2a4/go.mod h1:WQBcHRNX9shz3928lWEvstQJtAtYI7ks6XlgtRT9Tcw= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f h1:E87tDTVS5W65euzixn7clSzK66puSt1H4I5SC0EmHH4= +github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f/go.mod h1:3J2qVK16Lq8V+wfiL2lPeDZ7UWMxk5LemerHa1p6N00= +github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= +github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/cli v1.1.2 h1:PvH+lL2B7IQ101xQL63Of8yFS2y+aDlsFcsqNc+u/Kw= +github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo= +github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= +github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/oracle/oci-go-sdk v12.5.0+incompatible h1:pr08ECoaDKHWO9tnzJB1YqClEs7ZK1CFOez2DQocH14= +github.com/oracle/oci-go-sdk v12.5.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= +github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.6.2 h1:aIihoIOHCiLZHxyoNQ+ABL4NKhFTgKLBdMLyEAh98m0= +github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= +github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tencentcloud/tencentcloud-sdk-go v1.0.162/go.mod h1:asUz5BPXxgoPGaRgZaVm1iGcUAuHyYUo1nXqKa83cvI= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= +github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +github.com/yandex-cloud/go-genproto v0.0.0-20200722140432-762fe965ce77/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE= +github.com/yandex-cloud/go-sdk v0.0.0-20200722140627-2194e5077f13/go.mod h1:LEdAMqa1v/7KYe4b13ALLkonuDxLph57ibUb50ctvJk= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/zclconf/go-cty v1.9.1 h1:viqrgQwFl5UpSxc046qblj78wZXVDFnSOufaOTER+cc= +github.com/zclconf/go-cty v1.9.1/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.6 h1:BdkrbWrzDlV9dnbzoP7sfN+dHheJ4J9JOaYxcUDL+ok= +go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/goleak v1.0.0 h1:qsup4IcBdlmsnGfqyLl4Ntn3C2XCCuKAE7DwHpScyUo= +go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c h1:pkQiBZBvdos9qq4wBAHqlzuZHEXo07pqV06ef90u1WI= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef h1:fPxZ3Umkct3LZ8gK9nbk+DWDJ9fstZa2grBn+lWVKPs= +golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20210101214203-2dba1e4ea05c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200323114720-3f67cca34472/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83 h1:3V2dxSZpz4zozWWUq36vUxXEKnSYitEH2LdsAx+RUmg= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/plugins/host/mains/aws/main.go b/plugins/host/mains/aws/main.go new file mode 100644 index 000000000..5b488e769 --- /dev/null +++ b/plugins/host/mains/aws/main.go @@ -0,0 +1,18 @@ +package main + +import ( + "fmt" + "os" + + awshp "github.com/hashicorp/boundary-plugin-host-aws/plugin" + hp "github.com/hashicorp/boundary/sdk/plugins/host" + "github.com/hashicorp/go-hclog" +) + +func main() { + if err := hp.ServeHostPlugin(new(awshp.AwsPlugin), hp.WithLogger(hclog.NewNullLogger())); err != nil { + fmt.Println("Error serving plugin", err) + os.Exit(1) + } + os.Exit(0) +} diff --git a/plugins/host/mains/azure/go.mod b/plugins/host/mains/azure/go.mod new file mode 100644 index 000000000..e7af43ae7 --- /dev/null +++ b/plugins/host/mains/azure/go.mod @@ -0,0 +1,61 @@ +module github.com/hashicorp/boundary/plugins/host/mains/azure + +go 1.17 + +require ( + github.com/hashicorp/boundary-plugin-host-azure v0.0.0-20211110222619-3aff7f5aba17 + github.com/hashicorp/boundary/sdk v0.0.12-0.20211110201157-962c9463fdb3 + github.com/hashicorp/go-hclog v1.0.0 +) + +require ( + github.com/Azure/azure-sdk-for-go v59.2.0+incompatible // indirect + github.com/Azure/go-autorest v14.2.0+incompatible // indirect + github.com/Azure/go-autorest/autorest v0.11.22 // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.14 // indirect + github.com/Azure/go-autorest/autorest/azure/auth v0.5.9 // indirect + github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 // indirect + github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect + github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect + github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect + github.com/Azure/go-autorest/logger v0.2.1 // indirect + github.com/Azure/go-autorest/tracing v0.6.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dimchansky/utfbom v1.1.1 // indirect + github.com/fatih/color v1.12.0 // indirect + github.com/form3tech-oss/jwt-go v3.2.2+incompatible // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/eventlogger v0.1.1-0.20211104100552-e1e801e50144 // indirect + github.com/hashicorp/eventlogger/filters/encrypt v0.1.6-0.20211027211326-5db60a48f239 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-kms-wrapping v0.6.6 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-plugin v1.4.3 // indirect + github.com/hashicorp/go-retryablehttp v0.7.0 // indirect + github.com/hashicorp/go-uuid v1.0.2 // indirect + github.com/hashicorp/go-version v1.3.0 // indirect + github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect + github.com/manicminer/hamilton v0.33.0 // indirect + github.com/mattn/go-colorable v0.1.8 // indirect + github.com/mattn/go-isatty v0.0.13 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/go-testing-interface v1.0.0 // indirect + github.com/mitchellh/mapstructure v1.4.2 // indirect + github.com/mitchellh/pointerstructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/oklog/run v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.7.0 // indirect + golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect + golang.org/x/net v0.0.0-20210525063256-abc453219eb5 // indirect + golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c // indirect + golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect + golang.org/x/text v0.3.6 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6 // indirect + google.golang.org/grpc v1.40.0 // indirect + google.golang.org/protobuf v1.27.1 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/plugins/host/mains/azure/go.sum b/plugins/host/mains/azure/go.sum new file mode 100644 index 000000000..68aeec119 --- /dev/null +++ b/plugins/host/mains/azure/go.sum @@ -0,0 +1,877 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/azure-sdk-for-go v36.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v59.2.0+incompatible h1:mbxiZy1K820hQ+dI+YIO/+a0wQDYqOu18BAGe4lXjVk= +github.com/Azure/azure-sdk-for-go v59.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0= +github.com/Azure/go-autorest/autorest v0.10.1/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= +github.com/Azure/go-autorest/autorest v0.11.19/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= +github.com/Azure/go-autorest/autorest v0.11.22 h1:bXiQwDjrRmBQOE67bwlvUKAC1EU1yZTPQ38c+bstZws= +github.com/Azure/go-autorest/autorest v0.11.22/go.mod h1:BAWYUWGPEtKPzjVkp0Q6an0MJcJDsoh5Z1BFAEFs4Xs= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= +github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/adal v0.9.14 h1:G8hexQdV5D4khOXrWG2YuLCFKhWYmWD8bHYaXN5ophk= +github.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM= +github.com/Azure/go-autorest/autorest/azure/auth v0.5.9 h1:Y2CgdzitFDsdMwYMzf9LIZWrrTFysqbRc7b94XVVJ78= +github.com/Azure/go-autorest/autorest/azure/auth v0.5.9/go.mod h1:hg3/1yw0Bq87O3KvvnJoAh34/0zbP7SFizX/qN5JvjU= +github.com/Azure/go-autorest/autorest/azure/cli v0.3.1/go.mod h1:ZG5p860J94/0kI9mNJVoIoLgXcirM2gF5i2kWloofxw= +github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 h1:dMOmEJfkLKW/7JsokJqkyoYSgmR08hi9KrhjZb+JALY= +github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= +github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= +github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= +github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= +github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI= +github.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac= +github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= +github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= +github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190620160927-9418d7b0cd0f h1:oRD16bhpKNAanfcDDVU+J0NXqsgHIvGbbe/sy+r6Rs0= +github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190620160927-9418d7b0cd0f/go.mod h1:myCDvQSzCW+wB1WAlocEru4wMGJxy+vlxHdhegi1CDQ= +github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs= +github.com/armon/go-metrics v0.3.3/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.30.27 h1:9gPjZWVDSoQrBO2AvqrWObS6KAZByfEJxQoCYo4ZfK0= +github.com/aws/aws-sdk-go v1.30.27/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= +github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= +github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= +github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200709052629-daa8e1ccc0bc/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= +github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= +github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= +github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v1.4.2-0.20200319182547-c7ad2b866182/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc= +github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-ldap/ldap/v3 v3.1.3/go.mod h1:3rbOH3jRS2u6jg2rJnKAMLE/xQyCKIveG2Sa/Cohzb8= +github.com/go-ldap/ldap/v3 v3.1.10/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= +github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/boundary-plugin-host-azure v0.0.0-20211110222619-3aff7f5aba17 h1:zyGfx7GgdSOtnqVQDHQ8Qi+sJTrHIE7rBsAefL7Sa6I= +github.com/hashicorp/boundary-plugin-host-azure v0.0.0-20211110222619-3aff7f5aba17/go.mod h1:SmtW3WJ4MSQVmC4Fjs8tqI6EOrHw3LnwlTOvDhybcwk= +github.com/hashicorp/boundary/sdk v0.0.12-0.20211110201157-962c9463fdb3 h1:hl/QIALmFBG8bewvH6s/iB4H1kAXOT3DYreX9JTULIs= +github.com/hashicorp/boundary/sdk v0.0.12-0.20211110201157-962c9463fdb3/go.mod h1:gJoqOSNZfoclvLvBqAnfarha9YbZCtuRiYpu64GWX1Q= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/eventlogger v0.1.0/go.mod h1:a3IXf1aEJfpCPzseTOrwKj4fVW/Qn3oEmpQeaIznzH0= +github.com/hashicorp/eventlogger v0.1.1-0.20211104100552-e1e801e50144 h1:PCl0HtlVnIIloIozAKvjICu6K4IghKXAKNny3R3b2nI= +github.com/hashicorp/eventlogger v0.1.1-0.20211104100552-e1e801e50144/go.mod h1:a3IXf1aEJfpCPzseTOrwKj4fVW/Qn3oEmpQeaIznzH0= +github.com/hashicorp/eventlogger/filters/encrypt v0.1.6-0.20211027211326-5db60a48f239 h1:Yh9tY0lige+y0trmjQeT9NRDo6+YvtNAzbmUNOsIUzI= +github.com/hashicorp/eventlogger/filters/encrypt v0.1.6-0.20211027211326-5db60a48f239/go.mod h1:8rcez7Kw1zanB0/074qnOuGu7zxmNh9Xr2ZI+K4xVIA= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.16.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.0.0 h1:bkKf0BeBXcSYa7f5Fyi9gMuQ8gNsxeiNpZjR6VxNZeo= +github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-kms-wrapping v0.6.4/go.mod h1:pRK4UPSmNLEAjLE2qVz7nvS4NTZyyypQCn2G4q7vkps= +github.com/hashicorp/go-kms-wrapping v0.6.5/go.mod h1:9aLqFi2fqK1WMCyTe9bVjB0X6XxCxD+oINL1ovxpgJE= +github.com/hashicorp/go-kms-wrapping v0.6.6 h1:S2lw/8CFAw5kcztPD9iABG8ekjOs5san9C2oZrY6Syw= +github.com/hashicorp/go-kms-wrapping v0.6.6/go.mod h1:9aLqFi2fqK1WMCyTe9bVjB0X6XxCxD+oINL1ovxpgJE= +github.com/hashicorp/go-kms-wrapping/entropy v0.1.0/go.mod h1:d1g9WGtAunDNpek8jUIEJnBlbgKS1N2Q61QkHiZyR1g= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= +github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM= +github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-retryablehttp v0.6.2/go.mod h1:gEx6HMUGxYYhJScX7W1Il64m6cc2C1mDaW3NQ9sY1FY= +github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-retryablehttp v0.7.0 h1:eu1EI/mbirUgP5C8hVsTNaGZreBDlYiwC1FZWkvQPQ4= +github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-secure-stdlib/awsutil v0.1.1/go.mod h1:QRJZ7siKie+SZJB9jLbfKrs0Gd0yPWMtbneg0iU1PrY= +github.com/hashicorp/go-secure-stdlib/awsutil v0.1.2 h1:AEcLbDoaRC9JMmtZXsuCykztH53rvHsQFnwhoKtpNQM= +github.com/hashicorp/go-secure-stdlib/awsutil v0.1.2/go.mod h1:QRJZ7siKie+SZJB9jLbfKrs0Gd0yPWMtbneg0iU1PrY= +github.com/hashicorp/go-secure-stdlib/base62 v0.1.1/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw= +github.com/hashicorp/go-secure-stdlib/configutil v0.1.2 h1:ai0P91rxlyGWkYUH/zFr7mduW2Q+2FMrXawkLM8e7NU= +github.com/hashicorp/go-secure-stdlib/configutil v0.1.2/go.mod h1:EN1DJMjv9y5e/uRAuP2WPibAox5KHEiq2BjZl00aNPQ= +github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.1 h1:HlTofNQN49DTzPCMVDppp+AVxMNVWaHUElpUYC0TRRE= +github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.1/go.mod h1:irE9ILwhE8VooWdhHhBy0qXzOMRlHKoWoMlr31mAFCE= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.2 h1:Tz6v3Jb2DRnDCfifRSjYKG0m8dLdNq6bcDkB41en7nw= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.2/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= +github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1 h1:SMGUnbpAcat8rIKHkBPjfv81yC46a8eCNZ2hsR2l1EI= +github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1/go.mod h1:Ch/bf00Qnx77MZd49JRgHYqHQjtEmTgGU2faufpVZb0= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.1 h1:nd0HIW15E6FG1MsnArYaHfuw9C2zgzM8LxkG5Ty/788= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= +github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.1 h1:Yc026VyMyIpq1UWRnakHRG01U8fJm+nEfEmjoAb00n8= +github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.1/go.mod h1:l8slYwnJA26yBz+ErHpp2IRCLr0vuOMGBORIz4rRiAs= +github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw= +github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/vault/api v1.0.5-0.20200519221902-385fac77e20f/go.mod h1:euTFbi2YJgwcju3imEt919lhJKF68nN1cQPq3aA+kBE= +github.com/hashicorp/vault/api v1.1.1 h1:907ld+Z9cALyvbZK2qUX9cLwvSaEQsMVQB3x2KE8+AI= +github.com/hashicorp/vault/api v1.1.1/go.mod h1:29UXcn/1cLOPHQNMWA7bCz2By4PSd0VKPAydKXS5yN0= +github.com/hashicorp/vault/sdk v0.1.14-0.20200519221530-14615acda45f/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10= +github.com/hashicorp/vault/sdk v0.2.1 h1:S4O6Iv/dyKlE9AUTXGa7VOvZmsCvg36toPKgV4f2P4M= +github.com/hashicorp/vault/sdk v0.2.1/go.mod h1:WfUiO1vYzfBkz1TmoE4ZGU7HD0T0Cl/rZwaxjBkgN4U= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huaweicloud/golangsdk v0.0.0-20200304081349-45ec0797f2a4/go.mod h1:WQBcHRNX9shz3928lWEvstQJtAtYI7ks6XlgtRT9Tcw= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f h1:E87tDTVS5W65euzixn7clSzK66puSt1H4I5SC0EmHH4= +github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f/go.mod h1:3J2qVK16Lq8V+wfiL2lPeDZ7UWMxk5LemerHa1p6N00= +github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= +github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/manicminer/hamilton v0.33.0 h1:G58jBVHg2qsJqb5cPJ3tKzWj3JTf8gW3r5+4RmnEa0E= +github.com/manicminer/hamilton v0.33.0/go.mod h1:QryxpD/4+cdKuXNi0UjLDvgxYdP0LLmYz7dYU7DAX4U= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= +github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/cli v1.1.2 h1:PvH+lL2B7IQ101xQL63Of8yFS2y+aDlsFcsqNc+u/Kw= +github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo= +github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= +github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/oracle/oci-go-sdk v12.5.0+incompatible h1:pr08ECoaDKHWO9tnzJB1YqClEs7ZK1CFOez2DQocH14= +github.com/oracle/oci-go-sdk v12.5.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= +github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.6.2 h1:aIihoIOHCiLZHxyoNQ+ABL4NKhFTgKLBdMLyEAh98m0= +github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tencentcloud/tencentcloud-sdk-go v1.0.162/go.mod h1:asUz5BPXxgoPGaRgZaVm1iGcUAuHyYUo1nXqKa83cvI= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/yandex-cloud/go-genproto v0.0.0-20200722140432-762fe965ce77/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE= +github.com/yandex-cloud/go-sdk v0.0.0-20200722140627-2194e5077f13/go.mod h1:LEdAMqa1v/7KYe4b13ALLkonuDxLph57ibUb50ctvJk= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.6 h1:BdkrbWrzDlV9dnbzoP7sfN+dHheJ4J9JOaYxcUDL+ok= +go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/goleak v1.0.0 h1:qsup4IcBdlmsnGfqyLl4Ntn3C2XCCuKAE7DwHpScyUo= +go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5 h1:wjuX4b5yYQnEQHzd+CBcrcC6OVR2J1CN6mUy0oSxIPo= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c h1:pkQiBZBvdos9qq4wBAHqlzuZHEXo07pqV06ef90u1WI= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20210101214203-2dba1e4ea05c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.3 h1:L69ShwSZEyCsLKoAxDKeMvLDZkumEe8gXUZAjab0tX8= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200323114720-3f67cca34472/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6 h1:4Xw2NwItrJOFR5s6PnK98PI6Bgw1LhMP1j/rO5WP0S4= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/plugins/host/mains/azure/main.go b/plugins/host/mains/azure/main.go new file mode 100644 index 000000000..f1d22ead8 --- /dev/null +++ b/plugins/host/mains/azure/main.go @@ -0,0 +1,18 @@ +package main + +import ( + "fmt" + "os" + + azhp "github.com/hashicorp/boundary-plugin-host-azure/plugin" + hp "github.com/hashicorp/boundary/sdk/plugins/host" + "github.com/hashicorp/go-hclog" +) + +func main() { + if err := hp.ServeHostPlugin(new(azhp.AzurePlugin), hp.WithLogger(hclog.NewNullLogger())); err != nil { + fmt.Println("Error serving plugin", err) + os.Exit(1) + } + os.Exit(0) +} diff --git a/scripts/build.sh b/scripts/build.sh index 935563e66..edd91e553 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # -# This script builds the application from source for multiple platforms. +# This script builds the application from source for a single platform. set -e # Get the parent directory of where this script is. @@ -11,6 +11,10 @@ DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )" # Change into that directory cd "$DIR" +if [ "${CI_BUILD}x" != "x" ]; then + source /home/circleci/.bashrc +fi + # Set build tags BUILD_TAGS="${BUILD_TAGS:-"boundary"}" @@ -18,20 +22,13 @@ BUILD_TAGS="${BUILD_TAGS:-"boundary"}" GIT_COMMIT="$(git rev-parse HEAD)" GIT_DIRTY="$(test -n "`git status --porcelain`" && echo "+CHANGES" || true)" -# If its dev mode, only build for ourself -if [ "${BOUNDARY_DEV_BUILD}x" != "x" ] && [ "${XC_OSARCH}x" == "x" ]; then - XC_OS=$(go env GOOS) - XC_ARCH=$(go env GOARCH) - XC_OSARCH=$(go env GOOS)/$(go env GOARCH) -elif [ "${XC_OSARCH}x" != "x" ]; then - IFS='/' read -ra SPLITXC <<< "${XC_OSARCH}" - DEV_PLATFORM="./pkg/${SPLITXC[0]}_${SPLITXC[1]}" +# If not explicitly cross-compiling, build for the current platform +if [ "${GOOS}x" == "x" ]; then + GOOS=$(go env GOOS) +fi +if [ "${GOARCH}x" == "x" ]; then + GOARCH=$(go env GOARCH) fi - -# Determine the arch/os combos we're building for -XC_ARCH=${XC_ARCH:-"386 amd64"} -XC_OS=${XC_OS:-linux darwin windows freebsd openbsd netbsd solaris} -XC_OSARCH=${XC_OSARCH:-"linux/386 linux/amd64 linux/arm linux/arm64 darwin/386 darwin/amd64 windows/386 windows/amd64 freebsd/386 freebsd/amd64 freebsd/arm openbsd/386 openbsd/amd64 openbsd/arm netbsd/386 netbsd/amd64 solaris/amd64"} GOPATH=${GOPATH:-$(go env GOPATH)} case $(uname) in @@ -40,50 +37,49 @@ case $(uname) in ;; esac +BINARY_SUFFIX="" +if [ "${GOOS}x" == "windowsx" ]; then + BINARY_SUFFIX=".exe" +fi + +# Build needed plugins first +ORIG_PATH=$(pwd); +echo "==> Building Host Plugins..." +for PLUGIN_TYPE in host; do + rm -f $ORIG_PATH/plugins/$PLUGIN_TYPE/assets/boundary-plugin-${PLUGIN_TYPE}* + for CURR_PLUGIN in $(ls $ORIG_PATH/plugins/$PLUGIN_TYPE/mains); do + cd $ORIG_PATH/plugins/$PLUGIN_TYPE/mains/$CURR_PLUGIN; + go build -v -o $ORIG_PATH/plugins/$PLUGIN_TYPE/assets/boundary-plugin-${PLUGIN_TYPE}-${CURR_PLUGIN}${BINARY_SUFFIX} .; + cd $ORIG_PATH; + done; + cd $ORIG_PATH/plugins/$PLUGIN_TYPE/assets; + for CURR_PLUGIN in $(ls); do + gzip -f -9 $CURR_PLUGIN; + done; + cd $ORIG_PATH; +done; + +if [ "${CI_BUILD}x" != "x" ]; then + exit +fi + # Delete the old dir echo "==> Removing old directory..." rm -f bin/* -rm -rf pkg/* mkdir -p bin/ # Build! -# If GOX_PARALLEL_BUILDS is set, it will be used to add a "-parallel=${GOX_PARALLEL_BUILDS}" gox parameter echo "==> Building..." -gox \ - -osarch="${XC_OSARCH}" \ - -gcflags "${GCFLAGS}" \ - -ldflags "${LD_FLAGS}-X github.com/hashicorp/boundary/version.GitCommit=${GIT_COMMIT}${GIT_DIRTY}" \ - -output "pkg/{{.OS}}_{{.Arch}}/boundary" \ - ${GOX_PARALLEL_BUILDS+-parallel="${GOX_PARALLEL_BUILDS}"} \ - -tags="${BUILD_TAGS}" \ +BINARY_NAME="boundary${BINARY_SUFFIX}" +go build -tags="${BUILD_TAGS}" \ + -ldflags "-X github.com/hashicorp/boundary/version.GitCommit=${GIT_COMMIT}${GIT_DIRTY}" \ + -o "bin/${BINARY_NAME}" \ ./cmd/boundary -# Move all the compiled things to the $GOPATH/bin -OLDIFS=$IFS -IFS=: MAIN_GOPATH=($GOPATH) -IFS=$OLDIFS -# Copy our OS/Arch to the bin/ directory -DEV_PLATFORM=${DEV_PLATFORM:-"./pkg/$(go env GOOS)_$(go env GOARCH)"} -for F in $(find ${DEV_PLATFORM} -mindepth 1 -maxdepth 1 -type f); do - cp -f ${F} bin/ - cp -f ${F} ${MAIN_GOPATH}/bin/ -done - -if [ "${BOUNDARY_DEV_BUILD}x" = "x" ]; then - # Zip and copy to the dist dir - echo "==> Packaging..." - for PLATFORM in $(find ./pkg -mindepth 1 -maxdepth 1 -type d); do - OSARCH=$(basename ${PLATFORM}) - echo "--> ${OSARCH}" - - pushd $PLATFORM >/dev/null 2>&1 - zip ../${OSARCH}.zip ./* - popd >/dev/null 2>&1 - done -fi +# Copy binary into gopath +echo "==> Copying binary into GOPATH" +cp -f "bin/${BINARY_NAME}" "${GOPATH}/bin/" # Done! -echo -echo "==> Results:" -ls -hl bin/ +echo "==> Done!" diff --git a/sdk/go.mod b/sdk/go.mod index 5bdff1f8d..8fe2013f6 100644 --- a/sdk/go.mod +++ b/sdk/go.mod @@ -1,37 +1,106 @@ module github.com/hashicorp/boundary/sdk -go 1.15 +go 1.17 require ( - github.com/fatih/color v1.12.0 // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/google/go-cmp v0.5.6 // indirect github.com/hashicorp/eventlogger v0.1.1-0.20211106154408-4ff8da3a890c github.com/hashicorp/eventlogger/filters/encrypt v0.1.6-0.20211027211326-5db60a48f239 - github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-hclog v0.16.2 github.com/hashicorp/go-kms-wrapping v0.6.6 - github.com/hashicorp/go-retryablehttp v0.7.0 // indirect + github.com/hashicorp/go-plugin v1.0.1 github.com/hashicorp/go-secure-stdlib/configutil v0.1.2 github.com/hashicorp/go-secure-stdlib/strutil v0.1.1 + github.com/stretchr/testify v1.7.0 + google.golang.org/grpc v1.40.0 + google.golang.org/protobuf v1.27.1 +) + +require ( + cloud.google.com/go v0.65.0 // indirect + github.com/Azure/azure-sdk-for-go v36.2.0+incompatible // indirect + github.com/Azure/go-autorest/autorest v0.10.1 // indirect + github.com/Azure/go-autorest/autorest/adal v0.8.2 // indirect + github.com/Azure/go-autorest/autorest/azure/auth v0.4.2 // indirect + github.com/Azure/go-autorest/autorest/azure/cli v0.3.1 // indirect + github.com/Azure/go-autorest/autorest/date v0.2.0 // indirect + github.com/Azure/go-autorest/autorest/to v0.3.0 // indirect + github.com/Azure/go-autorest/autorest/validation v0.2.0 // indirect + github.com/Azure/go-autorest/logger v0.1.0 // indirect + github.com/Azure/go-autorest/tracing v0.5.0 // indirect + github.com/Masterminds/goutils v1.1.0 // indirect + github.com/Masterminds/semver v1.5.0 // indirect + github.com/Masterminds/sprig v2.22.0+incompatible // indirect + github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190620160927-9418d7b0cd0f // indirect + github.com/armon/go-radix v1.0.0 // indirect + github.com/aws/aws-sdk-go v1.30.27 // indirect + github.com/bgentry/speakeasy v0.1.0 // indirect + github.com/cenkalti/backoff/v3 v3.0.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect + github.com/dimchansky/utfbom v1.1.0 // indirect + github.com/fatih/color v1.12.0 // indirect + github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/snappy v0.0.1 // indirect + github.com/google/go-cmp v0.5.6 // indirect + github.com/google/uuid v1.1.2 // indirect + github.com/googleapis/gax-go/v2 v2.0.5 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-retryablehttp v0.7.0 // indirect + github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/go-secure-stdlib/awsutil v0.1.2 // indirect + github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.1 // indirect + github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1 // indirect + github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1 // indirect + github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.1 // indirect + github.com/hashicorp/go-sockaddr v1.0.2 // indirect + github.com/hashicorp/go-uuid v1.0.2 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/hashicorp/vault/api v1.1.1 // indirect + github.com/hashicorp/vault/sdk v0.2.1 // indirect + github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect + github.com/huandu/xstrings v1.3.2 // indirect + github.com/imdario/mergo v0.3.11 // indirect + github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f // indirect + github.com/jmespath/go-jmespath v0.3.0 // indirect github.com/json-iterator/go v1.1.10 // indirect github.com/kr/pretty v0.2.1 // indirect + github.com/mattn/go-colorable v0.1.8 // indirect github.com/mattn/go-isatty v0.0.13 // indirect + github.com/mitchellh/cli v1.1.2 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/go-testing-interface v1.0.0 // indirect + github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/mitchellh/pointerstructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/oklog/run v1.0.0 // indirect + github.com/oracle/oci-go-sdk v12.5.0+incompatible // indirect + github.com/pierrec/lz4 v2.5.2+incompatible // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/posener/complete v1.2.3 // indirect + github.com/ryanuber/go-glob v1.0.0 // indirect github.com/smartystreets/assertions v1.0.0 // indirect github.com/smartystreets/goconvey v1.6.4 // indirect github.com/stretchr/objx v0.2.0 // indirect - github.com/stretchr/testify v1.7.0 go.opencensus.io v0.22.6 // indirect golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93 // indirect golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect + golang.org/x/text v0.3.6 // indirect golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect golang.org/x/tools v0.1.3 // indirect + google.golang.org/api v0.30.0 // indirect + google.golang.org/appengine v1.6.6 // indirect google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6 // indirect - google.golang.org/grpc v1.38.0 // indirect - google.golang.org/protobuf v1.27.1 gopkg.in/ini.v1 v1.51.0 // indirect + gopkg.in/square/go-jose.v2 v2.5.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/sdk/go.sum b/sdk/go.sum index 702cc7d55..39861da94 100644 --- a/sdk/go.sum +++ b/sdk/go.sum @@ -75,6 +75,7 @@ github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZC github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -82,6 +83,7 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190620160927-9418d7b0cd0f h1:oRD16bhpKNAanfcDDVU+J0NXqsgHIvGbbe/sy+r6Rs0= github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190620160927-9418d7b0cd0f/go.mod h1:myCDvQSzCW+wB1WAlocEru4wMGJxy+vlxHdhegi1CDQ= github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs= github.com/armon/go-metrics v0.3.3/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= @@ -100,6 +102,7 @@ github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee/go.mod h1:S/7n9cop github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -109,6 +112,7 @@ github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= @@ -136,7 +140,8 @@ github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:Htrtb github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc= @@ -232,6 +237,7 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -262,6 +268,7 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-plugin v1.0.1 h1:4OtAfUGbnKC6yS48p0CtMX2oFYtzFZVv6rok3cRWgnE= github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.6.2/go.mod h1:gEx6HMUGxYYhJScX7W1Il64m6cc2C1mDaW3NQ9sY1FY= @@ -305,6 +312,7 @@ github.com/hashicorp/vault/api v1.1.1/go.mod h1:29UXcn/1cLOPHQNMWA7bCz2By4PSd0VK github.com/hashicorp/vault/sdk v0.1.14-0.20200519221530-14615acda45f/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10= github.com/hashicorp/vault/sdk v0.2.1 h1:S4O6Iv/dyKlE9AUTXGa7VOvZmsCvg36toPKgV4f2P4M= github.com/hashicorp/vault/sdk v0.2.1/go.mod h1:WfUiO1vYzfBkz1TmoE4ZGU7HD0T0Cl/rZwaxjBkgN4U= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= @@ -363,6 +371,7 @@ github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HK github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -382,6 +391,7 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -425,6 +435,7 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -441,6 +452,7 @@ github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUr github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -473,6 +485,7 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.6 h1:BdkrbWrzDlV9dnbzoP7sfN+dHheJ4J9JOaYxcUDL+ok= go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/goleak v1.0.0 h1:qsup4IcBdlmsnGfqyLl4Ntn3C2XCCuKAE7DwHpScyUo= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= @@ -521,7 +534,6 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -742,6 +754,7 @@ google.golang.org/genproto v0.0.0-20200323114720-3f67cca34472/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= @@ -765,9 +778,11 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -800,6 +815,7 @@ gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/sdk/pbs/controller/api/resources/hostcatalogs/host_catalog.pb.go b/sdk/pbs/controller/api/resources/hostcatalogs/host_catalog.pb.go index 636a2efc3..2b318fed2 100644 --- a/sdk/pbs/controller/api/resources/hostcatalogs/host_catalog.pb.go +++ b/sdk/pbs/controller/api/resources/hostcatalogs/host_catalog.pb.go @@ -7,6 +7,7 @@ package hostcatalogs import ( + plugins "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/plugins" scopes "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes" _ "github.com/hashicorp/boundary/sdk/pbs/controller/protooptions" protoreflect "google.golang.org/protobuf/reflect/protoreflect" @@ -37,6 +38,10 @@ type HostCatalog struct { ScopeId string `protobuf:"bytes,20,opt,name=scope_id,proto3" json:"scope_id,omitempty"` // Output only. Scope information for this resource. Scope *scopes.ScopeInfo `protobuf:"bytes,30,opt,name=scope,proto3" json:"scope,omitempty"` + // The ID of the plugin of which this catalog is created. + PluginId string `protobuf:"bytes,34,opt,name=plugin_id,proto3" json:"plugin_id,omitempty"` + // Output only. Plugin information for this resource. + Plugin *plugins.PluginInfo `protobuf:"bytes,35,opt,name=plugin,proto3" json:"plugin,omitempty"` // Optional name for identification purposes. Name *wrapperspb.StringValue `protobuf:"bytes,40,opt,name=name,proto3" json:"name,omitempty"` // Optional user-set description for identification purposes. @@ -52,6 +57,8 @@ type HostCatalog struct { Type string `protobuf:"bytes,90,opt,name=type,proto3" json:"type,omitempty"` // Attributes specific to the catalog type. Attributes *structpb.Struct `protobuf:"bytes,100,opt,name=attributes,proto3" json:"attributes,omitempty"` + // Secrets specific to the catalog type. These are never output. + Secrets *structpb.Struct `protobuf:"bytes,110,opt,name=secrets,proto3" json:"secrets,omitempty"` // Output only. The available actions on this resource for this user. AuthorizedActions []string `protobuf:"bytes,300,rep,name=authorized_actions,proto3" json:"authorized_actions,omitempty"` // Output only. The authorized actions for the scope's collections. @@ -111,6 +118,20 @@ func (x *HostCatalog) GetScope() *scopes.ScopeInfo { return nil } +func (x *HostCatalog) GetPluginId() string { + if x != nil { + return x.PluginId + } + return "" +} + +func (x *HostCatalog) GetPlugin() *plugins.PluginInfo { + if x != nil { + return x.Plugin + } + return nil +} + func (x *HostCatalog) GetName() *wrapperspb.StringValue { if x != nil { return x.Name @@ -160,6 +181,13 @@ func (x *HostCatalog) GetAttributes() *structpb.Struct { return nil } +func (x *HostCatalog) GetSecrets() *structpb.Struct { + if x != nil { + return x.Secrets + } + return nil +} + func (x *HostCatalog) GetAuthorizedActions() []string { if x != nil { return x.AuthorizedActions @@ -192,69 +220,83 @@ var file_controller_api_resources_hostcatalogs_v1_host_catalog_proto_rawDesc = [ 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2a, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, - 0x65, 0x72, 0x2f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x22, 0xd4, 0x06, 0x0a, 0x0b, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, - 0x6f, 0x67, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, - 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x14, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x12, 0x43, - 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, - 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2e, - 0x76, 0x31, 0x2e, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x73, 0x63, - 0x6f, 0x70, 0x65, 0x12, 0x46, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, - 0x14, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x0c, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x62, 0x0a, 0x0b, 0x64, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x32, 0x20, 0x01, 0x28, 0x0b, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x30, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, + 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, + 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x6c, 0x75, 0x67, + 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2a, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x6f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xfa, 0x07, 0x0a, 0x0b, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, + 0x61, 0x6c, 0x6f, 0x67, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, + 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, + 0x12, 0x43, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x63, 0x6f, 0x70, 0x65, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, + 0x73, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x22, 0x0a, 0x09, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x22, 0x20, 0x01, 0x28, 0x09, 0x42, 0x04, 0xa0, 0xda, 0x29, 0x01, 0x52, 0x09, + 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x12, 0x47, 0x0a, 0x06, 0x70, 0x6c, 0x75, + 0x67, 0x69, 0x6e, 0x18, 0x23, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x73, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, + 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x06, 0x70, 0x6c, 0x75, 0x67, + 0x69, 0x6e, 0x12, 0x46, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x22, - 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1a, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x3e, 0x0a, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, - 0x3c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, - 0x3e, 0x0a, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, - 0x46, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x52, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, - 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x50, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x5a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x3d, 0x0a, - 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x64, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x42, 0x04, 0xa0, 0xda, 0x29, 0x01, - 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x12, - 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x18, 0xac, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x9d, 0x01, - 0x0a, 0x1d, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6c, - 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, - 0xb6, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x56, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, - 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x73, 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x76, - 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x2e, 0x41, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x1d, - 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x6a, 0x0a, - 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x43, 0x6f, 0x6c, 0x6c, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x5a, 0x5a, 0x58, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, - 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x70, - 0x62, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, - 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x68, 0x6f, 0x73, 0x74, - 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x3b, 0x68, 0x6f, 0x73, 0x74, 0x63, 0x61, 0x74, - 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x14, + 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x0c, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x62, 0x0a, 0x0b, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x32, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x22, 0xa0, + 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1a, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3e, + 0x0a, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x3c, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x3e, + 0x0a, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x46, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x52, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x18, + 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x50, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x5a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x3d, 0x0a, 0x0a, + 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x64, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x42, 0x04, 0xa0, 0xda, 0x29, 0x01, 0x52, + 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x37, 0x0a, 0x07, 0x73, + 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x18, 0x6e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, + 0x74, 0x72, 0x75, 0x63, 0x74, 0x42, 0x04, 0xa0, 0xda, 0x29, 0x01, 0x52, 0x07, 0x73, 0x65, 0x63, + 0x72, 0x65, 0x74, 0x73, 0x12, 0x2f, 0x0a, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, + 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xac, 0x02, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x9d, 0x01, 0x0a, 0x1d, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xb6, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x56, + 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x63, 0x61, + 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, + 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, + 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x1d, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, + 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x6a, 0x0a, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x65, 0x64, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x42, 0x5a, 0x5a, 0x58, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, + 0x72, 0x79, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x62, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x73, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, + 0x3b, 0x68, 0x6f, 0x73, 0x74, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -274,25 +316,28 @@ var file_controller_api_resources_hostcatalogs_v1_host_catalog_proto_goTypes = [ (*HostCatalog)(nil), // 0: controller.api.resources.hostcatalogs.v1.HostCatalog nil, // 1: controller.api.resources.hostcatalogs.v1.HostCatalog.AuthorizedCollectionActionsEntry (*scopes.ScopeInfo)(nil), // 2: controller.api.resources.scopes.v1.ScopeInfo - (*wrapperspb.StringValue)(nil), // 3: google.protobuf.StringValue - (*timestamppb.Timestamp)(nil), // 4: google.protobuf.Timestamp - (*structpb.Struct)(nil), // 5: google.protobuf.Struct - (*structpb.ListValue)(nil), // 6: google.protobuf.ListValue + (*plugins.PluginInfo)(nil), // 3: controller.api.resources.plugins.v1.PluginInfo + (*wrapperspb.StringValue)(nil), // 4: google.protobuf.StringValue + (*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp + (*structpb.Struct)(nil), // 6: google.protobuf.Struct + (*structpb.ListValue)(nil), // 7: google.protobuf.ListValue } var file_controller_api_resources_hostcatalogs_v1_host_catalog_proto_depIdxs = []int32{ - 2, // 0: controller.api.resources.hostcatalogs.v1.HostCatalog.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo - 3, // 1: controller.api.resources.hostcatalogs.v1.HostCatalog.name:type_name -> google.protobuf.StringValue - 3, // 2: controller.api.resources.hostcatalogs.v1.HostCatalog.description:type_name -> google.protobuf.StringValue - 4, // 3: controller.api.resources.hostcatalogs.v1.HostCatalog.created_time:type_name -> google.protobuf.Timestamp - 4, // 4: controller.api.resources.hostcatalogs.v1.HostCatalog.updated_time:type_name -> google.protobuf.Timestamp - 5, // 5: controller.api.resources.hostcatalogs.v1.HostCatalog.attributes:type_name -> google.protobuf.Struct - 1, // 6: controller.api.resources.hostcatalogs.v1.HostCatalog.authorized_collection_actions:type_name -> controller.api.resources.hostcatalogs.v1.HostCatalog.AuthorizedCollectionActionsEntry - 6, // 7: controller.api.resources.hostcatalogs.v1.HostCatalog.AuthorizedCollectionActionsEntry.value:type_name -> google.protobuf.ListValue - 8, // [8:8] is the sub-list for method output_type - 8, // [8:8] is the sub-list for method input_type - 8, // [8:8] is the sub-list for extension type_name - 8, // [8:8] is the sub-list for extension extendee - 0, // [0:8] is the sub-list for field type_name + 2, // 0: controller.api.resources.hostcatalogs.v1.HostCatalog.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo + 3, // 1: controller.api.resources.hostcatalogs.v1.HostCatalog.plugin:type_name -> controller.api.resources.plugins.v1.PluginInfo + 4, // 2: controller.api.resources.hostcatalogs.v1.HostCatalog.name:type_name -> google.protobuf.StringValue + 4, // 3: controller.api.resources.hostcatalogs.v1.HostCatalog.description:type_name -> google.protobuf.StringValue + 5, // 4: controller.api.resources.hostcatalogs.v1.HostCatalog.created_time:type_name -> google.protobuf.Timestamp + 5, // 5: controller.api.resources.hostcatalogs.v1.HostCatalog.updated_time:type_name -> google.protobuf.Timestamp + 6, // 6: controller.api.resources.hostcatalogs.v1.HostCatalog.attributes:type_name -> google.protobuf.Struct + 6, // 7: controller.api.resources.hostcatalogs.v1.HostCatalog.secrets:type_name -> google.protobuf.Struct + 1, // 8: controller.api.resources.hostcatalogs.v1.HostCatalog.authorized_collection_actions:type_name -> controller.api.resources.hostcatalogs.v1.HostCatalog.AuthorizedCollectionActionsEntry + 7, // 9: controller.api.resources.hostcatalogs.v1.HostCatalog.AuthorizedCollectionActionsEntry.value:type_name -> google.protobuf.ListValue + 10, // [10:10] is the sub-list for method output_type + 10, // [10:10] is the sub-list for method input_type + 10, // [10:10] is the sub-list for extension type_name + 10, // [10:10] is the sub-list for extension extendee + 0, // [0:10] is the sub-list for field type_name } func init() { file_controller_api_resources_hostcatalogs_v1_host_catalog_proto_init() } diff --git a/sdk/pbs/controller/api/resources/hosts/host.pb.go b/sdk/pbs/controller/api/resources/hosts/host.pb.go index cca080e1e..e0dec3b5c 100644 --- a/sdk/pbs/controller/api/resources/hosts/host.pb.go +++ b/sdk/pbs/controller/api/resources/hosts/host.pb.go @@ -7,6 +7,7 @@ package hosts import ( + plugins "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/plugins" scopes "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes" _ "github.com/hashicorp/boundary/sdk/pbs/controller/protooptions" protoreflect "google.golang.org/protobuf/reflect/protoreflect" @@ -37,6 +38,8 @@ type Host struct { HostCatalogId string `protobuf:"bytes,20,opt,name=host_catalog_id,proto3" json:"host_catalog_id,omitempty"` // Output only. Scope information for this resource. Scope *scopes.ScopeInfo `protobuf:"bytes,30,opt,name=scope,proto3" json:"scope,omitempty"` + // Output only. Plugin information for this resource. + Plugin *plugins.PluginInfo `protobuf:"bytes,35,opt,name=plugin,proto3" json:"plugin,omitempty"` // Optional name for identification purposes. Name *wrapperspb.StringValue `protobuf:"bytes,40,opt,name=name,proto3" json:"name,omitempty"` // Optional user-set description for identification purposes. @@ -54,6 +57,10 @@ type Host struct { HostSetIds []string `protobuf:"bytes,100,rep,name=host_set_ids,proto3" json:"host_set_ids,omitempty"` // The attributes that are applicable to the specific Host type. Attributes *structpb.Struct `protobuf:"bytes,110,opt,name=attributes,proto3" json:"attributes,omitempty"` + // Output only. The list of ip addresses associated with this host. + IpAddresses []string `protobuf:"bytes,120,rep,name=ip_addresses,json=ipAddresses,proto3" json:"ip_addresses,omitempty"` + // Output only. The list of dns addresses associated with this host. + DnsNames []string `protobuf:"bytes,130,rep,name=dns_names,json=dnsNames,proto3" json:"dns_names,omitempty"` // Output only. The available actions on this resource for this user. AuthorizedActions []string `protobuf:"bytes,300,rep,name=authorized_actions,proto3" json:"authorized_actions,omitempty"` } @@ -111,6 +118,13 @@ func (x *Host) GetScope() *scopes.ScopeInfo { return nil } +func (x *Host) GetPlugin() *plugins.PluginInfo { + if x != nil { + return x.Plugin + } + return nil +} + func (x *Host) GetName() *wrapperspb.StringValue { if x != nil { return x.Name @@ -167,6 +181,20 @@ func (x *Host) GetAttributes() *structpb.Struct { return nil } +func (x *Host) GetIpAddresses() []string { + if x != nil { + return x.IpAddresses + } + return nil +} + +func (x *Host) GetDnsNames() []string { + if x != nil { + return x.DnsNames + } + return nil +} + func (x *Host) GetAuthorizedActions() []string { if x != nil { return x.AuthorizedActions @@ -239,61 +267,73 @@ var file_controller_api_resources_hosts_v1_host_proto_rawDesc = []byte{ 0x1a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x1a, 0x2a, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x63, 0x75, 0x73, - 0x74, 0x6f, 0x6d, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x6f, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xf3, 0x04, 0x0a, - 0x04, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x28, 0x0a, 0x0f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, - 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, - 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x12, - 0x43, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, - 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, - 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x73, - 0x63, 0x6f, 0x70, 0x65, 0x12, 0x46, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x28, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x42, 0x14, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x0c, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x12, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x62, 0x0a, 0x0b, - 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x32, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, - 0x22, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1a, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x3e, 0x0a, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, - 0x18, 0x3c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, - 0x6d, 0x70, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, - 0x12, 0x3e, 0x0a, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, - 0x18, 0x46, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, - 0x6d, 0x70, 0x52, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, - 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x50, 0x20, 0x01, 0x28, - 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, - 0x70, 0x65, 0x18, 0x5a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x22, - 0x0a, 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x64, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x69, - 0x64, 0x73, 0x12, 0x3d, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, - 0x18, 0x6e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x42, - 0x04, 0xa0, 0xda, 0x29, 0x01, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, - 0x73, 0x12, 0x2f, 0x0a, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xac, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, - 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x22, 0x75, 0x0a, 0x14, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x48, 0x6f, 0x73, 0x74, - 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x5d, 0x0a, 0x07, 0x61, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, - 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x25, 0xa0, 0xda, 0x29, 0x01, 0xc2, - 0xdd, 0x29, 0x1d, 0x0a, 0x12, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, - 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x42, 0x4c, 0x5a, 0x4a, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, - 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x70, - 0x62, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, - 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x68, 0x6f, 0x73, 0x74, - 0x73, 0x3b, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x1a, 0x30, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, + 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, + 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x1a, 0x2a, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x63, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31, + 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xfd, + 0x05, 0x0a, 0x04, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x28, 0x0a, 0x0f, 0x68, 0x6f, 0x73, 0x74, 0x5f, + 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, + 0x64, 0x12, 0x43, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x63, 0x6f, 0x70, + 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, + 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x47, 0x0a, 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, + 0x18, 0x23, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x73, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c, 0x75, + 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x12, + 0x46, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x14, 0xa0, 0xda, 0x29, + 0x01, 0xc2, 0xdd, 0x29, 0x0c, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x62, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x32, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x22, 0xa0, 0xda, 0x29, 0x01, + 0xc2, 0xdd, 0x29, 0x1a, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, 0x0c, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x3c, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x3e, 0x0a, 0x0c, 0x75, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x46, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x75, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x50, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x5a, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x68, 0x6f, 0x73, + 0x74, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x64, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x12, 0x3d, 0x0a, + 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x6e, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x42, 0x04, 0xa0, 0xda, 0x29, 0x01, + 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, + 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x78, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x0b, 0x69, 0x70, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, + 0x1c, 0x0a, 0x09, 0x64, 0x6e, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x82, 0x01, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x08, 0x64, 0x6e, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x2f, 0x0a, + 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x18, 0xac, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x61, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x75, + 0x0a, 0x14, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x48, 0x6f, 0x73, 0x74, 0x41, 0x74, 0x74, 0x72, + 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x5d, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x25, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1d, 0x0a, + 0x12, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x12, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x42, 0x4c, 0x5a, 0x4a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, + 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x62, 0x73, 0x2f, 0x63, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x3b, 0x68, 0x6f, + 0x73, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -313,23 +353,25 @@ var file_controller_api_resources_hosts_v1_host_proto_goTypes = []interface{}{ (*Host)(nil), // 0: controller.api.resources.hosts.v1.Host (*StaticHostAttributes)(nil), // 1: controller.api.resources.hosts.v1.StaticHostAttributes (*scopes.ScopeInfo)(nil), // 2: controller.api.resources.scopes.v1.ScopeInfo - (*wrapperspb.StringValue)(nil), // 3: google.protobuf.StringValue - (*timestamppb.Timestamp)(nil), // 4: google.protobuf.Timestamp - (*structpb.Struct)(nil), // 5: google.protobuf.Struct + (*plugins.PluginInfo)(nil), // 3: controller.api.resources.plugins.v1.PluginInfo + (*wrapperspb.StringValue)(nil), // 4: google.protobuf.StringValue + (*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp + (*structpb.Struct)(nil), // 6: google.protobuf.Struct } var file_controller_api_resources_hosts_v1_host_proto_depIdxs = []int32{ 2, // 0: controller.api.resources.hosts.v1.Host.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo - 3, // 1: controller.api.resources.hosts.v1.Host.name:type_name -> google.protobuf.StringValue - 3, // 2: controller.api.resources.hosts.v1.Host.description:type_name -> google.protobuf.StringValue - 4, // 3: controller.api.resources.hosts.v1.Host.created_time:type_name -> google.protobuf.Timestamp - 4, // 4: controller.api.resources.hosts.v1.Host.updated_time:type_name -> google.protobuf.Timestamp - 5, // 5: controller.api.resources.hosts.v1.Host.attributes:type_name -> google.protobuf.Struct - 3, // 6: controller.api.resources.hosts.v1.StaticHostAttributes.address:type_name -> google.protobuf.StringValue - 7, // [7:7] is the sub-list for method output_type - 7, // [7:7] is the sub-list for method input_type - 7, // [7:7] is the sub-list for extension type_name - 7, // [7:7] is the sub-list for extension extendee - 0, // [0:7] is the sub-list for field type_name + 3, // 1: controller.api.resources.hosts.v1.Host.plugin:type_name -> controller.api.resources.plugins.v1.PluginInfo + 4, // 2: controller.api.resources.hosts.v1.Host.name:type_name -> google.protobuf.StringValue + 4, // 3: controller.api.resources.hosts.v1.Host.description:type_name -> google.protobuf.StringValue + 5, // 4: controller.api.resources.hosts.v1.Host.created_time:type_name -> google.protobuf.Timestamp + 5, // 5: controller.api.resources.hosts.v1.Host.updated_time:type_name -> google.protobuf.Timestamp + 6, // 6: controller.api.resources.hosts.v1.Host.attributes:type_name -> google.protobuf.Struct + 4, // 7: controller.api.resources.hosts.v1.StaticHostAttributes.address:type_name -> google.protobuf.StringValue + 8, // [8:8] is the sub-list for method output_type + 8, // [8:8] is the sub-list for method input_type + 8, // [8:8] is the sub-list for extension type_name + 8, // [8:8] is the sub-list for extension extendee + 0, // [0:8] is the sub-list for field type_name } func init() { file_controller_api_resources_hosts_v1_host_proto_init() } diff --git a/sdk/pbs/controller/api/resources/hostsets/host_set.pb.go b/sdk/pbs/controller/api/resources/hostsets/host_set.pb.go index 04b03c2b1..f87ceffde 100644 --- a/sdk/pbs/controller/api/resources/hostsets/host_set.pb.go +++ b/sdk/pbs/controller/api/resources/hostsets/host_set.pb.go @@ -7,6 +7,7 @@ package hostsets import ( + plugins "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/plugins" scopes "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes" _ "github.com/hashicorp/boundary/sdk/pbs/controller/protooptions" protoreflect "google.golang.org/protobuf/reflect/protoreflect" @@ -37,6 +38,8 @@ type HostSet struct { HostCatalogId string `protobuf:"bytes,20,opt,name=host_catalog_id,proto3" json:"host_catalog_id,omitempty"` // Output only. Scope information for this resource. Scope *scopes.ScopeInfo `protobuf:"bytes,30,opt,name=scope,proto3" json:"scope,omitempty"` + // Output only. Plugin information for this resource. + Plugin *plugins.PluginInfo `protobuf:"bytes,35,opt,name=plugin,proto3" json:"plugin,omitempty"` // Optional name for identification purposes. Name *wrapperspb.StringValue `protobuf:"bytes,40,opt,name=name,proto3" json:"name,omitempty"` // Optional user-set description for identification purposes. @@ -52,6 +55,20 @@ type HostSet struct { Type string `protobuf:"bytes,90,opt,name=type,proto3" json:"type,omitempty"` // Output only. A list of Hosts in this Host Set. HostIds []string `protobuf:"bytes,100,rep,name=host_ids,proto3" json:"host_ids,omitempty"` + // multiple possible endpoints for a host. Preferences are specified by + // "cidr:" or "dns:", specifying which IP + // address or DNS name out of a host's available possibilities should be + // preferred. If no preferences are specified, a value will be chosen from + // among all avialable values using a built-in priority order. May not be + // valid for all plugin types. + PreferredEndpoints []string `protobuf:"bytes,101,rep,name=preferred_endpoints,proto3" json:"preferred_endpoints,omitempty"` + // An interger number of seconds indicating the amount of time that should + // elapse between syncs of the host set. The interval will be applied to the + // end of the previous sync operation, not the start. Setting to any + // negative value will disable syncing for that host set; setting to zero + // will cause the set to use Boundary's default. The default may change + // between releases. May not be valid for all plugin types. + SyncIntervalSeconds *wrapperspb.Int32Value `protobuf:"bytes,102,opt,name=sync_interval_seconds,proto3" json:"sync_interval_seconds,omitempty"` // The attributes that are applicable for the specific Host Set type. Attributes *structpb.Struct `protobuf:"bytes,110,opt,name=attributes,proto3" json:"attributes,omitempty"` // Output only. The available actions on this resource for this user. @@ -111,6 +128,13 @@ func (x *HostSet) GetScope() *scopes.ScopeInfo { return nil } +func (x *HostSet) GetPlugin() *plugins.PluginInfo { + if x != nil { + return x.Plugin + } + return nil +} + func (x *HostSet) GetName() *wrapperspb.StringValue { if x != nil { return x.Name @@ -160,6 +184,20 @@ func (x *HostSet) GetHostIds() []string { return nil } +func (x *HostSet) GetPreferredEndpoints() []string { + if x != nil { + return x.PreferredEndpoints + } + return nil +} + +func (x *HostSet) GetSyncIntervalSeconds() *wrapperspb.Int32Value { + if x != nil { + return x.SyncIntervalSeconds + } + return nil +} + func (x *HostSet) GetAttributes() *structpb.Struct { if x != nil { return x.Attributes @@ -191,45 +229,68 @@ var file_controller_api_resources_hostsets_v1_host_set_proto_rawDesc = []byte{ 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x73, - 0x63, 0x6f, 0x70, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2a, 0x63, 0x6f, 0x6e, 0x74, - 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x6f, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe8, 0x04, 0x0a, 0x07, 0x48, 0x6f, 0x73, 0x74, 0x53, - 0x65, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, - 0x69, 0x64, 0x12, 0x28, 0x0a, 0x0f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, 0x6c, - 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x68, 0x6f, 0x73, - 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x12, 0x43, 0x0a, 0x05, - 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f, - 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, - 0x2e, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, - 0x65, 0x12, 0x46, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x14, 0xa0, - 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x0c, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x62, 0x0a, 0x0b, 0x64, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x32, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, + 0x63, 0x6f, 0x70, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x30, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x73, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x2f, + 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2a, 0x63, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, + 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa6, 0x07, 0x0a, 0x07, 0x48, 0x6f, 0x73, + 0x74, 0x53, 0x65, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x02, 0x69, 0x64, 0x12, 0x28, 0x0a, 0x0f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, + 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x68, + 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x12, 0x43, + 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x73, 0x63, + 0x6f, 0x70, 0x65, 0x12, 0x47, 0x0a, 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x18, 0x23, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x70, + 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, + 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x12, 0x46, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, + 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x14, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, + 0x29, 0x0c, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x62, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x32, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x22, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, + 0x1a, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, 0x0c, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x3c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x22, 0xa0, 0xda, - 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1a, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, - 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x3c, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, - 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x3e, 0x0a, - 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x46, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, - 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, - 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x50, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x5a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x68, - 0x6f, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x64, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x68, - 0x6f, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x12, 0x37, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, - 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x6e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, - 0x72, 0x75, 0x63, 0x74, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x3e, 0x0a, 0x0c, 0x75, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x46, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x75, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x18, 0x50, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x5a, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x69, + 0x64, 0x73, 0x18, 0x64, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x69, + 0x64, 0x73, 0x12, 0x63, 0x0a, 0x13, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x5f, + 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x65, 0x20, 0x03, 0x28, 0x09, 0x42, + 0x31, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x29, 0x0a, 0x13, 0x70, 0x72, 0x65, 0x66, 0x65, + 0x72, 0x72, 0x65, 0x64, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x12, + 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x73, 0x52, 0x13, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x5f, 0x65, 0x6e, + 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x87, 0x01, 0x0a, 0x15, 0x73, 0x79, 0x6e, 0x63, + 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, + 0x73, 0x18, 0x66, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x42, 0x34, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x2c, 0x0a, 0x15, + 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x73, 0x65, + 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x13, 0x53, 0x79, 0x6e, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, + 0x76, 0x61, 0x6c, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x52, 0x15, 0x73, 0x79, 0x6e, 0x63, + 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, + 0x73, 0x12, 0x3d, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, + 0x6e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x42, 0x04, + 0xa0, 0xda, 0x29, 0x01, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xac, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, @@ -257,22 +318,26 @@ var file_controller_api_resources_hostsets_v1_host_set_proto_msgTypes = make([]p var file_controller_api_resources_hostsets_v1_host_set_proto_goTypes = []interface{}{ (*HostSet)(nil), // 0: controller.api.resources.hostsets.v1.HostSet (*scopes.ScopeInfo)(nil), // 1: controller.api.resources.scopes.v1.ScopeInfo - (*wrapperspb.StringValue)(nil), // 2: google.protobuf.StringValue - (*timestamppb.Timestamp)(nil), // 3: google.protobuf.Timestamp - (*structpb.Struct)(nil), // 4: google.protobuf.Struct + (*plugins.PluginInfo)(nil), // 2: controller.api.resources.plugins.v1.PluginInfo + (*wrapperspb.StringValue)(nil), // 3: google.protobuf.StringValue + (*timestamppb.Timestamp)(nil), // 4: google.protobuf.Timestamp + (*wrapperspb.Int32Value)(nil), // 5: google.protobuf.Int32Value + (*structpb.Struct)(nil), // 6: google.protobuf.Struct } var file_controller_api_resources_hostsets_v1_host_set_proto_depIdxs = []int32{ 1, // 0: controller.api.resources.hostsets.v1.HostSet.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo - 2, // 1: controller.api.resources.hostsets.v1.HostSet.name:type_name -> google.protobuf.StringValue - 2, // 2: controller.api.resources.hostsets.v1.HostSet.description:type_name -> google.protobuf.StringValue - 3, // 3: controller.api.resources.hostsets.v1.HostSet.created_time:type_name -> google.protobuf.Timestamp - 3, // 4: controller.api.resources.hostsets.v1.HostSet.updated_time:type_name -> google.protobuf.Timestamp - 4, // 5: controller.api.resources.hostsets.v1.HostSet.attributes:type_name -> google.protobuf.Struct - 6, // [6:6] is the sub-list for method output_type - 6, // [6:6] is the sub-list for method input_type - 6, // [6:6] is the sub-list for extension type_name - 6, // [6:6] is the sub-list for extension extendee - 0, // [0:6] is the sub-list for field type_name + 2, // 1: controller.api.resources.hostsets.v1.HostSet.plugin:type_name -> controller.api.resources.plugins.v1.PluginInfo + 3, // 2: controller.api.resources.hostsets.v1.HostSet.name:type_name -> google.protobuf.StringValue + 3, // 3: controller.api.resources.hostsets.v1.HostSet.description:type_name -> google.protobuf.StringValue + 4, // 4: controller.api.resources.hostsets.v1.HostSet.created_time:type_name -> google.protobuf.Timestamp + 4, // 5: controller.api.resources.hostsets.v1.HostSet.updated_time:type_name -> google.protobuf.Timestamp + 5, // 6: controller.api.resources.hostsets.v1.HostSet.sync_interval_seconds:type_name -> google.protobuf.Int32Value + 6, // 7: controller.api.resources.hostsets.v1.HostSet.attributes:type_name -> google.protobuf.Struct + 8, // [8:8] is the sub-list for method output_type + 8, // [8:8] is the sub-list for method input_type + 8, // [8:8] is the sub-list for extension type_name + 8, // [8:8] is the sub-list for extension extendee + 0, // [0:8] is the sub-list for field type_name } func init() { file_controller_api_resources_hostsets_v1_host_set_proto_init() } diff --git a/sdk/pbs/controller/api/resources/plugins/plugin.pb.go b/sdk/pbs/controller/api/resources/plugins/plugin.pb.go new file mode 100644 index 000000000..b670e6a36 --- /dev/null +++ b/sdk/pbs/controller/api/resources/plugins/plugin.pb.go @@ -0,0 +1,172 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.17.3 +// source: controller/api/resources/plugins/v1/plugin.proto + +package plugins + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type PluginInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Output only. The ID of the Plugin. + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // Output only. The name of the plugin resource in boundary, if any. + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + // Output only. The description of the plugin in boundary, if any. + Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` +} + +func (x *PluginInfo) Reset() { + *x = PluginInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_resources_plugins_v1_plugin_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PluginInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PluginInfo) ProtoMessage() {} + +func (x *PluginInfo) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_resources_plugins_v1_plugin_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PluginInfo.ProtoReflect.Descriptor instead. +func (*PluginInfo) Descriptor() ([]byte, []int) { + return file_controller_api_resources_plugins_v1_plugin_proto_rawDescGZIP(), []int{0} +} + +func (x *PluginInfo) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *PluginInfo) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *PluginInfo) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +var File_controller_api_resources_plugins_v1_plugin_proto protoreflect.FileDescriptor + +var file_controller_api_resources_plugins_v1_plugin_proto_rawDesc = []byte{ + 0x0a, 0x30, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, + 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, + 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x12, 0x23, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x70, 0x6c, 0x75, + 0x67, 0x69, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x22, 0x52, 0x0a, 0x0a, 0x50, 0x6c, 0x75, 0x67, 0x69, + 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x50, 0x5a, 0x4e, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, + 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x73, 0x64, 0x6b, + 0x2f, 0x70, 0x62, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, + 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x70, 0x6c, + 0x75, 0x67, 0x69, 0x6e, 0x73, 0x3b, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_controller_api_resources_plugins_v1_plugin_proto_rawDescOnce sync.Once + file_controller_api_resources_plugins_v1_plugin_proto_rawDescData = file_controller_api_resources_plugins_v1_plugin_proto_rawDesc +) + +func file_controller_api_resources_plugins_v1_plugin_proto_rawDescGZIP() []byte { + file_controller_api_resources_plugins_v1_plugin_proto_rawDescOnce.Do(func() { + file_controller_api_resources_plugins_v1_plugin_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_api_resources_plugins_v1_plugin_proto_rawDescData) + }) + return file_controller_api_resources_plugins_v1_plugin_proto_rawDescData +} + +var file_controller_api_resources_plugins_v1_plugin_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_controller_api_resources_plugins_v1_plugin_proto_goTypes = []interface{}{ + (*PluginInfo)(nil), // 0: controller.api.resources.plugins.v1.PluginInfo +} +var file_controller_api_resources_plugins_v1_plugin_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_controller_api_resources_plugins_v1_plugin_proto_init() } +func file_controller_api_resources_plugins_v1_plugin_proto_init() { + if File_controller_api_resources_plugins_v1_plugin_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_controller_api_resources_plugins_v1_plugin_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PluginInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_controller_api_resources_plugins_v1_plugin_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_controller_api_resources_plugins_v1_plugin_proto_goTypes, + DependencyIndexes: file_controller_api_resources_plugins_v1_plugin_proto_depIdxs, + MessageInfos: file_controller_api_resources_plugins_v1_plugin_proto_msgTypes, + }.Build() + File_controller_api_resources_plugins_v1_plugin_proto = out.File + file_controller_api_resources_plugins_v1_plugin_proto_rawDesc = nil + file_controller_api_resources_plugins_v1_plugin_proto_goTypes = nil + file_controller_api_resources_plugins_v1_plugin_proto_depIdxs = nil +} diff --git a/sdk/pbs/plugin/host_plugin_service.pb.go b/sdk/pbs/plugin/host_plugin_service.pb.go new file mode 100644 index 000000000..951f8d127 --- /dev/null +++ b/sdk/pbs/plugin/host_plugin_service.pb.go @@ -0,0 +1,1456 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.17.3 +// source: plugin/v1/host_plugin_service.proto + +package plugin + +import ( + hostcatalogs "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs" + _ "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hosts" + hostsets "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + structpb "google.golang.org/protobuf/types/known/structpb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type OnCreateCatalogRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The host catalog to create. The request may contain optional + // secret data to help authenticate the request against a cloud + // API. + Catalog *hostcatalogs.HostCatalog `protobuf:"bytes,10,opt,name=catalog,proto3" json:"catalog,omitempty"` +} + +func (x *OnCreateCatalogRequest) Reset() { + *x = OnCreateCatalogRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OnCreateCatalogRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OnCreateCatalogRequest) ProtoMessage() {} + +func (x *OnCreateCatalogRequest) ProtoReflect() protoreflect.Message { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OnCreateCatalogRequest.ProtoReflect.Descriptor instead. +func (*OnCreateCatalogRequest) Descriptor() ([]byte, []int) { + return file_plugin_v1_host_plugin_service_proto_rawDescGZIP(), []int{0} +} + +func (x *OnCreateCatalogRequest) GetCatalog() *hostcatalogs.HostCatalog { + if x != nil { + return x.Catalog + } + return nil +} + +type OnCreateCatalogResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Secret data to persist encrypted within Boundary. This should be used to + // store authentication data and other necessary configuration to be used in + // later hooks and calls. Returning an error from the call will cause this + // data to not be persisted. If this is nil, nothing is written. + Persisted *HostCatalogPersisted `protobuf:"bytes,10,opt,name=persisted,proto3" json:"persisted,omitempty"` +} + +func (x *OnCreateCatalogResponse) Reset() { + *x = OnCreateCatalogResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OnCreateCatalogResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OnCreateCatalogResponse) ProtoMessage() {} + +func (x *OnCreateCatalogResponse) ProtoReflect() protoreflect.Message { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OnCreateCatalogResponse.ProtoReflect.Descriptor instead. +func (*OnCreateCatalogResponse) Descriptor() ([]byte, []int) { + return file_plugin_v1_host_plugin_service_proto_rawDescGZIP(), []int{1} +} + +func (x *OnCreateCatalogResponse) GetPersisted() *HostCatalogPersisted { + if x != nil { + return x.Persisted + } + return nil +} + +type OnUpdateCatalogRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The existing state of the catalog. + CurrentCatalog *hostcatalogs.HostCatalog `protobuf:"bytes,10,opt,name=current_catalog,json=currentCatalog,proto3" json:"current_catalog,omitempty"` + // The requested new state of the catalog. This field may contain optional + // secret data that may have been updated from old authentication data + // contained within the persisted state. + NewCatalog *hostcatalogs.HostCatalog `protobuf:"bytes,20,opt,name=new_catalog,json=newCatalog,proto3" json:"new_catalog,omitempty"` + // The existing persisted secret data. + Persisted *HostCatalogPersisted `protobuf:"bytes,30,opt,name=persisted,proto3" json:"persisted,omitempty"` +} + +func (x *OnUpdateCatalogRequest) Reset() { + *x = OnUpdateCatalogRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OnUpdateCatalogRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OnUpdateCatalogRequest) ProtoMessage() {} + +func (x *OnUpdateCatalogRequest) ProtoReflect() protoreflect.Message { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OnUpdateCatalogRequest.ProtoReflect.Descriptor instead. +func (*OnUpdateCatalogRequest) Descriptor() ([]byte, []int) { + return file_plugin_v1_host_plugin_service_proto_rawDescGZIP(), []int{2} +} + +func (x *OnUpdateCatalogRequest) GetCurrentCatalog() *hostcatalogs.HostCatalog { + if x != nil { + return x.CurrentCatalog + } + return nil +} + +func (x *OnUpdateCatalogRequest) GetNewCatalog() *hostcatalogs.HostCatalog { + if x != nil { + return x.NewCatalog + } + return nil +} + +func (x *OnUpdateCatalogRequest) GetPersisted() *HostCatalogPersisted { + if x != nil { + return x.Persisted + } + return nil +} + +type OnUpdateCatalogResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The updated secret data to persist encrypted within Boundary. If an error + // is returned, the update of the persisted data is aborted. If this is nil, + // no changes are written. To remove all values, simply return an allocated + // but empty map. + Persisted *HostCatalogPersisted `protobuf:"bytes,10,opt,name=persisted,proto3" json:"persisted,omitempty"` +} + +func (x *OnUpdateCatalogResponse) Reset() { + *x = OnUpdateCatalogResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OnUpdateCatalogResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OnUpdateCatalogResponse) ProtoMessage() {} + +func (x *OnUpdateCatalogResponse) ProtoReflect() protoreflect.Message { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OnUpdateCatalogResponse.ProtoReflect.Descriptor instead. +func (*OnUpdateCatalogResponse) Descriptor() ([]byte, []int) { + return file_plugin_v1_host_plugin_service_proto_rawDescGZIP(), []int{3} +} + +func (x *OnUpdateCatalogResponse) GetPersisted() *HostCatalogPersisted { + if x != nil { + return x.Persisted + } + return nil +} + +type OnDeleteCatalogRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The existing state of the catalog to delete. + Catalog *hostcatalogs.HostCatalog `protobuf:"bytes,10,opt,name=catalog,proto3" json:"catalog,omitempty"` + // The host sets contained in the catalog being deleted. + Sets []*hostsets.HostSet `protobuf:"bytes,20,rep,name=sets,proto3" json:"sets,omitempty"` + // The existing persisted secret data. + Persisted *HostCatalogPersisted `protobuf:"bytes,30,opt,name=persisted,proto3" json:"persisted,omitempty"` +} + +func (x *OnDeleteCatalogRequest) Reset() { + *x = OnDeleteCatalogRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OnDeleteCatalogRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OnDeleteCatalogRequest) ProtoMessage() {} + +func (x *OnDeleteCatalogRequest) ProtoReflect() protoreflect.Message { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OnDeleteCatalogRequest.ProtoReflect.Descriptor instead. +func (*OnDeleteCatalogRequest) Descriptor() ([]byte, []int) { + return file_plugin_v1_host_plugin_service_proto_rawDescGZIP(), []int{4} +} + +func (x *OnDeleteCatalogRequest) GetCatalog() *hostcatalogs.HostCatalog { + if x != nil { + return x.Catalog + } + return nil +} + +func (x *OnDeleteCatalogRequest) GetSets() []*hostsets.HostSet { + if x != nil { + return x.Sets + } + return nil +} + +func (x *OnDeleteCatalogRequest) GetPersisted() *HostCatalogPersisted { + if x != nil { + return x.Persisted + } + return nil +} + +type OnDeleteCatalogResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *OnDeleteCatalogResponse) Reset() { + *x = OnDeleteCatalogResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OnDeleteCatalogResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OnDeleteCatalogResponse) ProtoMessage() {} + +func (x *OnDeleteCatalogResponse) ProtoReflect() protoreflect.Message { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OnDeleteCatalogResponse.ProtoReflect.Descriptor instead. +func (*OnDeleteCatalogResponse) Descriptor() ([]byte, []int) { + return file_plugin_v1_host_plugin_service_proto_rawDescGZIP(), []int{5} +} + +type OnCreateSetRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The host catalog that the set belongs to. + Catalog *hostcatalogs.HostCatalog `protobuf:"bytes,10,opt,name=catalog,proto3" json:"catalog,omitempty"` + // The host set to create. + Set *hostsets.HostSet `protobuf:"bytes,20,opt,name=set,proto3" json:"set,omitempty"` + // The persisted data for the host catalog that the set belongs to. + Persisted *HostCatalogPersisted `protobuf:"bytes,30,opt,name=persisted,proto3" json:"persisted,omitempty"` +} + +func (x *OnCreateSetRequest) Reset() { + *x = OnCreateSetRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OnCreateSetRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OnCreateSetRequest) ProtoMessage() {} + +func (x *OnCreateSetRequest) ProtoReflect() protoreflect.Message { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OnCreateSetRequest.ProtoReflect.Descriptor instead. +func (*OnCreateSetRequest) Descriptor() ([]byte, []int) { + return file_plugin_v1_host_plugin_service_proto_rawDescGZIP(), []int{6} +} + +func (x *OnCreateSetRequest) GetCatalog() *hostcatalogs.HostCatalog { + if x != nil { + return x.Catalog + } + return nil +} + +func (x *OnCreateSetRequest) GetSet() *hostsets.HostSet { + if x != nil { + return x.Set + } + return nil +} + +func (x *OnCreateSetRequest) GetPersisted() *HostCatalogPersisted { + if x != nil { + return x.Persisted + } + return nil +} + +type OnCreateSetResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *OnCreateSetResponse) Reset() { + *x = OnCreateSetResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OnCreateSetResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OnCreateSetResponse) ProtoMessage() {} + +func (x *OnCreateSetResponse) ProtoReflect() protoreflect.Message { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OnCreateSetResponse.ProtoReflect.Descriptor instead. +func (*OnCreateSetResponse) Descriptor() ([]byte, []int) { + return file_plugin_v1_host_plugin_service_proto_rawDescGZIP(), []int{7} +} + +type OnUpdateSetRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The host catalog that the set belongs to. + Catalog *hostcatalogs.HostCatalog `protobuf:"bytes,10,opt,name=catalog,proto3" json:"catalog,omitempty"` + // The existing state of the host set. + CurrentSet *hostsets.HostSet `protobuf:"bytes,20,opt,name=current_set,json=currentSet,proto3" json:"current_set,omitempty"` + // The requested new state of the host set. + NewSet *hostsets.HostSet `protobuf:"bytes,30,opt,name=new_set,json=newSet,proto3" json:"new_set,omitempty"` + // The persisted data for the host catalog that the set belongs to. + Persisted *HostCatalogPersisted `protobuf:"bytes,40,opt,name=persisted,proto3" json:"persisted,omitempty"` +} + +func (x *OnUpdateSetRequest) Reset() { + *x = OnUpdateSetRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OnUpdateSetRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OnUpdateSetRequest) ProtoMessage() {} + +func (x *OnUpdateSetRequest) ProtoReflect() protoreflect.Message { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OnUpdateSetRequest.ProtoReflect.Descriptor instead. +func (*OnUpdateSetRequest) Descriptor() ([]byte, []int) { + return file_plugin_v1_host_plugin_service_proto_rawDescGZIP(), []int{8} +} + +func (x *OnUpdateSetRequest) GetCatalog() *hostcatalogs.HostCatalog { + if x != nil { + return x.Catalog + } + return nil +} + +func (x *OnUpdateSetRequest) GetCurrentSet() *hostsets.HostSet { + if x != nil { + return x.CurrentSet + } + return nil +} + +func (x *OnUpdateSetRequest) GetNewSet() *hostsets.HostSet { + if x != nil { + return x.NewSet + } + return nil +} + +func (x *OnUpdateSetRequest) GetPersisted() *HostCatalogPersisted { + if x != nil { + return x.Persisted + } + return nil +} + +type OnUpdateSetResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *OnUpdateSetResponse) Reset() { + *x = OnUpdateSetResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OnUpdateSetResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OnUpdateSetResponse) ProtoMessage() {} + +func (x *OnUpdateSetResponse) ProtoReflect() protoreflect.Message { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OnUpdateSetResponse.ProtoReflect.Descriptor instead. +func (*OnUpdateSetResponse) Descriptor() ([]byte, []int) { + return file_plugin_v1_host_plugin_service_proto_rawDescGZIP(), []int{9} +} + +type OnDeleteSetRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The host catalog that the set belongs to. + Catalog *hostcatalogs.HostCatalog `protobuf:"bytes,10,opt,name=catalog,proto3" json:"catalog,omitempty"` + // The host set to delete. + Set *hostsets.HostSet `protobuf:"bytes,20,opt,name=set,proto3" json:"set,omitempty"` + // The persisted data for the host catalog that the set belongs to. + Persisted *HostCatalogPersisted `protobuf:"bytes,30,opt,name=persisted,proto3" json:"persisted,omitempty"` +} + +func (x *OnDeleteSetRequest) Reset() { + *x = OnDeleteSetRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OnDeleteSetRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OnDeleteSetRequest) ProtoMessage() {} + +func (x *OnDeleteSetRequest) ProtoReflect() protoreflect.Message { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OnDeleteSetRequest.ProtoReflect.Descriptor instead. +func (*OnDeleteSetRequest) Descriptor() ([]byte, []int) { + return file_plugin_v1_host_plugin_service_proto_rawDescGZIP(), []int{10} +} + +func (x *OnDeleteSetRequest) GetCatalog() *hostcatalogs.HostCatalog { + if x != nil { + return x.Catalog + } + return nil +} + +func (x *OnDeleteSetRequest) GetSet() *hostsets.HostSet { + if x != nil { + return x.Set + } + return nil +} + +func (x *OnDeleteSetRequest) GetPersisted() *HostCatalogPersisted { + if x != nil { + return x.Persisted + } + return nil +} + +type OnDeleteSetResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *OnDeleteSetResponse) Reset() { + *x = OnDeleteSetResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OnDeleteSetResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OnDeleteSetResponse) ProtoMessage() {} + +func (x *OnDeleteSetResponse) ProtoReflect() protoreflect.Message { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OnDeleteSetResponse.ProtoReflect.Descriptor instead. +func (*OnDeleteSetResponse) Descriptor() ([]byte, []int) { + return file_plugin_v1_host_plugin_service_proto_rawDescGZIP(), []int{11} +} + +type ListHostsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The host catalog that the supplied host sets belong to. + Catalog *hostcatalogs.HostCatalog `protobuf:"bytes,10,opt,name=catalog,proto3" json:"catalog,omitempty"` + // The host sets to look up hosts for. + Sets []*hostsets.HostSet `protobuf:"bytes,20,rep,name=sets,proto3" json:"sets,omitempty"` + // The persisted data for the host catalog that the supplied host + // sets belong to. + Persisted *HostCatalogPersisted `protobuf:"bytes,30,opt,name=persisted,proto3" json:"persisted,omitempty"` +} + +func (x *ListHostsRequest) Reset() { + *x = ListHostsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListHostsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListHostsRequest) ProtoMessage() {} + +func (x *ListHostsRequest) ProtoReflect() protoreflect.Message { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListHostsRequest.ProtoReflect.Descriptor instead. +func (*ListHostsRequest) Descriptor() ([]byte, []int) { + return file_plugin_v1_host_plugin_service_proto_rawDescGZIP(), []int{12} +} + +func (x *ListHostsRequest) GetCatalog() *hostcatalogs.HostCatalog { + if x != nil { + return x.Catalog + } + return nil +} + +func (x *ListHostsRequest) GetSets() []*hostsets.HostSet { + if x != nil { + return x.Sets + } + return nil +} + +func (x *ListHostsRequest) GetPersisted() *HostCatalogPersisted { + if x != nil { + return x.Persisted + } + return nil +} + +type ListHostsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The hosts to return. + Hosts []*ListHostsResponseHost `protobuf:"bytes,10,rep,name=hosts,proto3" json:"hosts,omitempty"` +} + +func (x *ListHostsResponse) Reset() { + *x = ListHostsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListHostsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListHostsResponse) ProtoMessage() {} + +func (x *ListHostsResponse) ProtoReflect() protoreflect.Message { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListHostsResponse.ProtoReflect.Descriptor instead. +func (*ListHostsResponse) Descriptor() ([]byte, []int) { + return file_plugin_v1_host_plugin_service_proto_rawDescGZIP(), []int{13} +} + +func (x *ListHostsResponse) GetHosts() []*ListHostsResponseHost { + if x != nil { + return x.Hosts + } + return nil +} + +type ListHostsResponseHost struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Required. A stable identifier for this host. This field is used + // to generate a stable ID for the host within Boundary and is + // included in audit logs. It should be set to something unique and + // useful, ie: a compute instance ID. + ExternalId string `protobuf:"bytes,10,opt,name=external_id,json=externalId,proto3" json:"external_id,omitempty"` + // If supplied, will be set as the managed name of the host + Name string `protobuf:"bytes,20,opt,name=name,proto3" json:"name,omitempty"` + // If supplied, will be set as the managed description of the host + Description string `protobuf:"bytes,30,opt,name=description,proto3" json:"description,omitempty"` + // Required. Any and all known IP addresses for the host. + IpAddresses []string `protobuf:"bytes,40,rep,name=ip_addresses,json=ipAddresses,proto3" json:"ip_addresses,omitempty"` + // Required. Any and all known DNS names for the host. + DnsNames []string `protobuf:"bytes,50,rep,name=dns_names,json=dnsNames,proto3" json:"dns_names,omitempty"` + // Required. The host set IDs that match this host, out of the host sets + // sent in the request. + SetIds []string `protobuf:"bytes,60,rep,name=set_ids,json=setIds,proto3" json:"set_ids,omitempty"` + // Optional. Provider-specific metadata that is applicable to this + // host. Example: host descriptions, tags, alternate network + // addresses, etc. + Attributes *structpb.Struct `protobuf:"bytes,100,opt,name=attributes,proto3" json:"attributes,omitempty"` +} + +func (x *ListHostsResponseHost) Reset() { + *x = ListHostsResponseHost{} + if protoimpl.UnsafeEnabled { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListHostsResponseHost) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListHostsResponseHost) ProtoMessage() {} + +func (x *ListHostsResponseHost) ProtoReflect() protoreflect.Message { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListHostsResponseHost.ProtoReflect.Descriptor instead. +func (*ListHostsResponseHost) Descriptor() ([]byte, []int) { + return file_plugin_v1_host_plugin_service_proto_rawDescGZIP(), []int{14} +} + +func (x *ListHostsResponseHost) GetExternalId() string { + if x != nil { + return x.ExternalId + } + return "" +} + +func (x *ListHostsResponseHost) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ListHostsResponseHost) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *ListHostsResponseHost) GetIpAddresses() []string { + if x != nil { + return x.IpAddresses + } + return nil +} + +func (x *ListHostsResponseHost) GetDnsNames() []string { + if x != nil { + return x.DnsNames + } + return nil +} + +func (x *ListHostsResponseHost) GetSetIds() []string { + if x != nil { + return x.SetIds + } + return nil +} + +func (x *ListHostsResponseHost) GetAttributes() *structpb.Struct { + if x != nil { + return x.Attributes + } + return nil +} + +// HostCatalogPersisted represents state persisted between host catalog calls. +// Its intended purpose is to store authentication data required by the plugin +// to make calls to its respective cloud API. +// +// The secrets stored in this message are encrypted at-rest by Boundary and +// never returned to the end user. +// +// TODO: Add a size limit to this data before we export the plugin SDK. +type HostCatalogPersisted struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The persisted secrets. + Secrets *structpb.Struct `protobuf:"bytes,100,opt,name=secrets,proto3" json:"secrets,omitempty"` +} + +func (x *HostCatalogPersisted) Reset() { + *x = HostCatalogPersisted{} + if protoimpl.UnsafeEnabled { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HostCatalogPersisted) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HostCatalogPersisted) ProtoMessage() {} + +func (x *HostCatalogPersisted) ProtoReflect() protoreflect.Message { + mi := &file_plugin_v1_host_plugin_service_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HostCatalogPersisted.ProtoReflect.Descriptor instead. +func (*HostCatalogPersisted) Descriptor() ([]byte, []int) { + return file_plugin_v1_host_plugin_service_proto_rawDescGZIP(), []int{15} +} + +func (x *HostCatalogPersisted) GetSecrets() *structpb.Struct { + if x != nil { + return x.Secrets + } + return nil +} + +var File_plugin_v1_host_plugin_service_proto protoreflect.FileDescriptor + +var file_plugin_v1_host_plugin_service_proto_rawDesc = []byte{ + 0x0a, 0x23, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x76, 0x31, 0x2f, 0x68, 0x6f, 0x73, 0x74, + 0x5f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31, + 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x3b, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x63, 0x61, 0x74, + 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, + 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x33, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x65, 0x74, 0x73, 0x2f, 0x76, + 0x31, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x1a, 0x2c, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, + 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x73, + 0x2f, 0x76, 0x31, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x69, + 0x0a, 0x16, 0x4f, 0x6e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, + 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4f, 0x0a, 0x07, 0x63, 0x61, 0x74, 0x61, + 0x6c, 0x6f, 0x67, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x73, 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, + 0x52, 0x07, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x22, 0x58, 0x0a, 0x17, 0x4f, 0x6e, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x09, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, + 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, + 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x50, + 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x64, 0x52, 0x09, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, + 0x74, 0x65, 0x64, 0x22, 0x8f, 0x02, 0x0a, 0x16, 0x4f, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x5e, + 0x0a, 0x0f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, + 0x67, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x73, 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x0e, + 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x12, 0x56, + 0x0a, 0x0b, 0x6e, 0x65, 0x77, 0x5f, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x18, 0x14, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x68, + 0x6f, 0x73, 0x74, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x48, + 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x0a, 0x6e, 0x65, 0x77, 0x43, + 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x12, 0x3d, 0x0a, 0x09, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, + 0x74, 0x65, 0x64, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x6c, 0x75, 0x67, + 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, + 0x67, 0x50, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x64, 0x52, 0x09, 0x70, 0x65, 0x72, 0x73, + 0x69, 0x73, 0x74, 0x65, 0x64, 0x22, 0x58, 0x0a, 0x17, 0x4f, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x3d, 0x0a, 0x09, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x64, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, + 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x50, 0x65, 0x72, 0x73, 0x69, + 0x73, 0x74, 0x65, 0x64, 0x52, 0x09, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x64, 0x22, + 0xeb, 0x01, 0x0a, 0x16, 0x4f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x61, 0x74, 0x61, + 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4f, 0x0a, 0x07, 0x63, 0x61, + 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x63, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x63, 0x61, 0x74, 0x61, 0x6c, + 0x6f, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, + 0x6f, 0x67, 0x52, 0x07, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x12, 0x41, 0x0a, 0x04, 0x73, + 0x65, 0x74, 0x73, 0x18, 0x14, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x73, 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, + 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x52, 0x04, 0x73, 0x65, 0x74, 0x73, 0x12, 0x3d, + 0x0a, 0x09, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x64, 0x18, 0x1e, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, + 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x50, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, + 0x65, 0x64, 0x52, 0x09, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x64, 0x22, 0x19, 0x0a, + 0x17, 0x4f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xe5, 0x01, 0x0a, 0x12, 0x4f, 0x6e, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x4f, 0x0a, 0x07, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x68, 0x6f, 0x73, 0x74, + 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, + 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x07, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, + 0x12, 0x3f, 0x0a, 0x03, 0x73, 0x65, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x65, 0x74, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x52, 0x03, 0x73, 0x65, + 0x74, 0x12, 0x3d, 0x0a, 0x09, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x64, 0x18, 0x1e, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31, + 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x50, 0x65, 0x72, 0x73, + 0x69, 0x73, 0x74, 0x65, 0x64, 0x52, 0x09, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x64, + 0x22, 0x15, 0x0a, 0x13, 0x4f, 0x6e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x65, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xbc, 0x02, 0x0a, 0x12, 0x4f, 0x6e, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4f, + 0x0a, 0x07, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x63, + 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, + 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x07, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x12, + 0x4e, 0x0a, 0x0b, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x18, 0x14, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, + 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, + 0x68, 0x6f, 0x73, 0x74, 0x73, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, + 0x53, 0x65, 0x74, 0x52, 0x0a, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x74, 0x12, + 0x46, 0x0a, 0x07, 0x6e, 0x65, 0x77, 0x5f, 0x73, 0x65, 0x74, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x68, 0x6f, 0x73, 0x74, + 0x73, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x52, + 0x06, 0x6e, 0x65, 0x77, 0x53, 0x65, 0x74, 0x12, 0x3d, 0x0a, 0x09, 0x70, 0x65, 0x72, 0x73, 0x69, + 0x73, 0x74, 0x65, 0x64, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x6c, 0x75, + 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, + 0x6f, 0x67, 0x50, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x64, 0x52, 0x09, 0x70, 0x65, 0x72, + 0x73, 0x69, 0x73, 0x74, 0x65, 0x64, 0x22, 0x15, 0x0a, 0x13, 0x4f, 0x6e, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xe5, 0x01, + 0x0a, 0x12, 0x4f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x4f, 0x0a, 0x07, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, + 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, + 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x76, 0x31, + 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x07, 0x63, 0x61, + 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x12, 0x3f, 0x0a, 0x03, 0x73, 0x65, 0x74, 0x18, 0x14, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x68, 0x6f, + 0x73, 0x74, 0x73, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, + 0x74, 0x52, 0x03, 0x73, 0x65, 0x74, 0x12, 0x3d, 0x0a, 0x09, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, + 0x74, 0x65, 0x64, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x6c, 0x75, 0x67, + 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, + 0x67, 0x50, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x64, 0x52, 0x09, 0x70, 0x65, 0x72, 0x73, + 0x69, 0x73, 0x74, 0x65, 0x64, 0x22, 0x15, 0x0a, 0x13, 0x4f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xe5, 0x01, 0x0a, + 0x10, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x4f, 0x0a, 0x07, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x68, 0x6f, + 0x73, 0x74, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, + 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x07, 0x63, 0x61, 0x74, 0x61, 0x6c, + 0x6f, 0x67, 0x12, 0x41, 0x0a, 0x04, 0x73, 0x65, 0x74, 0x73, 0x18, 0x14, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x68, 0x6f, 0x73, 0x74, + 0x73, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x52, + 0x04, 0x73, 0x65, 0x74, 0x73, 0x12, 0x3d, 0x0a, 0x09, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, + 0x65, 0x64, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, + 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, + 0x50, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x64, 0x52, 0x09, 0x70, 0x65, 0x72, 0x73, 0x69, + 0x73, 0x74, 0x65, 0x64, 0x22, 0x4b, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x05, 0x68, 0x6f, 0x73, + 0x74, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, + 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x05, 0x68, 0x6f, 0x73, 0x74, + 0x73, 0x22, 0x80, 0x02, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x65, + 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x65, 0x73, 0x18, 0x28, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x70, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x6e, 0x73, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x73, 0x18, 0x32, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x64, 0x6e, 0x73, 0x4e, 0x61, 0x6d, + 0x65, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x3c, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x74, 0x49, 0x64, 0x73, 0x12, 0x37, 0x0a, 0x0a, 0x61, + 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x64, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, + 0x75, 0x74, 0x65, 0x73, 0x22, 0x49, 0x0a, 0x14, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, + 0x6c, 0x6f, 0x67, 0x50, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x64, 0x12, 0x31, 0x0a, 0x07, + 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x18, 0x64, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x07, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x32, + 0xd3, 0x04, 0x0a, 0x11, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x58, 0x0a, 0x0f, 0x4f, 0x6e, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x12, 0x21, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, + 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x61, 0x74, + 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x70, 0x6c, + 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x58, 0x0a, 0x0f, 0x4f, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x61, 0x74, 0x61, 0x6c, + 0x6f, 0x67, 0x12, 0x21, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, + 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, + 0x31, 0x2e, 0x4f, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, + 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x0f, 0x4f, 0x6e, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x12, 0x21, 0x2e, 0x70, + 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x22, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6e, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x4f, 0x6e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, + 0x65, 0x74, 0x12, 0x1d, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, + 0x6e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1e, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6e, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x4f, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x74, + 0x12, 0x1d, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6e, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1e, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6e, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x4c, 0x0a, 0x0b, 0x4f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x65, 0x74, 0x12, 0x1d, + 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6e, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, + 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6e, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, + 0x09, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x70, 0x6c, 0x75, + 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, + 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x35, 0x5a, 0x33, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, + 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x62, 0x73, 0x2f, 0x70, + 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x3b, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_plugin_v1_host_plugin_service_proto_rawDescOnce sync.Once + file_plugin_v1_host_plugin_service_proto_rawDescData = file_plugin_v1_host_plugin_service_proto_rawDesc +) + +func file_plugin_v1_host_plugin_service_proto_rawDescGZIP() []byte { + file_plugin_v1_host_plugin_service_proto_rawDescOnce.Do(func() { + file_plugin_v1_host_plugin_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_plugin_v1_host_plugin_service_proto_rawDescData) + }) + return file_plugin_v1_host_plugin_service_proto_rawDescData +} + +var file_plugin_v1_host_plugin_service_proto_msgTypes = make([]protoimpl.MessageInfo, 16) +var file_plugin_v1_host_plugin_service_proto_goTypes = []interface{}{ + (*OnCreateCatalogRequest)(nil), // 0: plugin.v1.OnCreateCatalogRequest + (*OnCreateCatalogResponse)(nil), // 1: plugin.v1.OnCreateCatalogResponse + (*OnUpdateCatalogRequest)(nil), // 2: plugin.v1.OnUpdateCatalogRequest + (*OnUpdateCatalogResponse)(nil), // 3: plugin.v1.OnUpdateCatalogResponse + (*OnDeleteCatalogRequest)(nil), // 4: plugin.v1.OnDeleteCatalogRequest + (*OnDeleteCatalogResponse)(nil), // 5: plugin.v1.OnDeleteCatalogResponse + (*OnCreateSetRequest)(nil), // 6: plugin.v1.OnCreateSetRequest + (*OnCreateSetResponse)(nil), // 7: plugin.v1.OnCreateSetResponse + (*OnUpdateSetRequest)(nil), // 8: plugin.v1.OnUpdateSetRequest + (*OnUpdateSetResponse)(nil), // 9: plugin.v1.OnUpdateSetResponse + (*OnDeleteSetRequest)(nil), // 10: plugin.v1.OnDeleteSetRequest + (*OnDeleteSetResponse)(nil), // 11: plugin.v1.OnDeleteSetResponse + (*ListHostsRequest)(nil), // 12: plugin.v1.ListHostsRequest + (*ListHostsResponse)(nil), // 13: plugin.v1.ListHostsResponse + (*ListHostsResponseHost)(nil), // 14: plugin.v1.ListHostsResponseHost + (*HostCatalogPersisted)(nil), // 15: plugin.v1.HostCatalogPersisted + (*hostcatalogs.HostCatalog)(nil), // 16: controller.api.resources.hostcatalogs.v1.HostCatalog + (*hostsets.HostSet)(nil), // 17: controller.api.resources.hostsets.v1.HostSet + (*structpb.Struct)(nil), // 18: google.protobuf.Struct +} +var file_plugin_v1_host_plugin_service_proto_depIdxs = []int32{ + 16, // 0: plugin.v1.OnCreateCatalogRequest.catalog:type_name -> controller.api.resources.hostcatalogs.v1.HostCatalog + 15, // 1: plugin.v1.OnCreateCatalogResponse.persisted:type_name -> plugin.v1.HostCatalogPersisted + 16, // 2: plugin.v1.OnUpdateCatalogRequest.current_catalog:type_name -> controller.api.resources.hostcatalogs.v1.HostCatalog + 16, // 3: plugin.v1.OnUpdateCatalogRequest.new_catalog:type_name -> controller.api.resources.hostcatalogs.v1.HostCatalog + 15, // 4: plugin.v1.OnUpdateCatalogRequest.persisted:type_name -> plugin.v1.HostCatalogPersisted + 15, // 5: plugin.v1.OnUpdateCatalogResponse.persisted:type_name -> plugin.v1.HostCatalogPersisted + 16, // 6: plugin.v1.OnDeleteCatalogRequest.catalog:type_name -> controller.api.resources.hostcatalogs.v1.HostCatalog + 17, // 7: plugin.v1.OnDeleteCatalogRequest.sets:type_name -> controller.api.resources.hostsets.v1.HostSet + 15, // 8: plugin.v1.OnDeleteCatalogRequest.persisted:type_name -> plugin.v1.HostCatalogPersisted + 16, // 9: plugin.v1.OnCreateSetRequest.catalog:type_name -> controller.api.resources.hostcatalogs.v1.HostCatalog + 17, // 10: plugin.v1.OnCreateSetRequest.set:type_name -> controller.api.resources.hostsets.v1.HostSet + 15, // 11: plugin.v1.OnCreateSetRequest.persisted:type_name -> plugin.v1.HostCatalogPersisted + 16, // 12: plugin.v1.OnUpdateSetRequest.catalog:type_name -> controller.api.resources.hostcatalogs.v1.HostCatalog + 17, // 13: plugin.v1.OnUpdateSetRequest.current_set:type_name -> controller.api.resources.hostsets.v1.HostSet + 17, // 14: plugin.v1.OnUpdateSetRequest.new_set:type_name -> controller.api.resources.hostsets.v1.HostSet + 15, // 15: plugin.v1.OnUpdateSetRequest.persisted:type_name -> plugin.v1.HostCatalogPersisted + 16, // 16: plugin.v1.OnDeleteSetRequest.catalog:type_name -> controller.api.resources.hostcatalogs.v1.HostCatalog + 17, // 17: plugin.v1.OnDeleteSetRequest.set:type_name -> controller.api.resources.hostsets.v1.HostSet + 15, // 18: plugin.v1.OnDeleteSetRequest.persisted:type_name -> plugin.v1.HostCatalogPersisted + 16, // 19: plugin.v1.ListHostsRequest.catalog:type_name -> controller.api.resources.hostcatalogs.v1.HostCatalog + 17, // 20: plugin.v1.ListHostsRequest.sets:type_name -> controller.api.resources.hostsets.v1.HostSet + 15, // 21: plugin.v1.ListHostsRequest.persisted:type_name -> plugin.v1.HostCatalogPersisted + 14, // 22: plugin.v1.ListHostsResponse.hosts:type_name -> plugin.v1.ListHostsResponseHost + 18, // 23: plugin.v1.ListHostsResponseHost.attributes:type_name -> google.protobuf.Struct + 18, // 24: plugin.v1.HostCatalogPersisted.secrets:type_name -> google.protobuf.Struct + 0, // 25: plugin.v1.HostPluginService.OnCreateCatalog:input_type -> plugin.v1.OnCreateCatalogRequest + 2, // 26: plugin.v1.HostPluginService.OnUpdateCatalog:input_type -> plugin.v1.OnUpdateCatalogRequest + 4, // 27: plugin.v1.HostPluginService.OnDeleteCatalog:input_type -> plugin.v1.OnDeleteCatalogRequest + 6, // 28: plugin.v1.HostPluginService.OnCreateSet:input_type -> plugin.v1.OnCreateSetRequest + 8, // 29: plugin.v1.HostPluginService.OnUpdateSet:input_type -> plugin.v1.OnUpdateSetRequest + 10, // 30: plugin.v1.HostPluginService.OnDeleteSet:input_type -> plugin.v1.OnDeleteSetRequest + 12, // 31: plugin.v1.HostPluginService.ListHosts:input_type -> plugin.v1.ListHostsRequest + 1, // 32: plugin.v1.HostPluginService.OnCreateCatalog:output_type -> plugin.v1.OnCreateCatalogResponse + 3, // 33: plugin.v1.HostPluginService.OnUpdateCatalog:output_type -> plugin.v1.OnUpdateCatalogResponse + 5, // 34: plugin.v1.HostPluginService.OnDeleteCatalog:output_type -> plugin.v1.OnDeleteCatalogResponse + 7, // 35: plugin.v1.HostPluginService.OnCreateSet:output_type -> plugin.v1.OnCreateSetResponse + 9, // 36: plugin.v1.HostPluginService.OnUpdateSet:output_type -> plugin.v1.OnUpdateSetResponse + 11, // 37: plugin.v1.HostPluginService.OnDeleteSet:output_type -> plugin.v1.OnDeleteSetResponse + 13, // 38: plugin.v1.HostPluginService.ListHosts:output_type -> plugin.v1.ListHostsResponse + 32, // [32:39] is the sub-list for method output_type + 25, // [25:32] is the sub-list for method input_type + 25, // [25:25] is the sub-list for extension type_name + 25, // [25:25] is the sub-list for extension extendee + 0, // [0:25] is the sub-list for field type_name +} + +func init() { file_plugin_v1_host_plugin_service_proto_init() } +func file_plugin_v1_host_plugin_service_proto_init() { + if File_plugin_v1_host_plugin_service_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_plugin_v1_host_plugin_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OnCreateCatalogRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_plugin_v1_host_plugin_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OnCreateCatalogResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_plugin_v1_host_plugin_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OnUpdateCatalogRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_plugin_v1_host_plugin_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OnUpdateCatalogResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_plugin_v1_host_plugin_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OnDeleteCatalogRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_plugin_v1_host_plugin_service_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OnDeleteCatalogResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_plugin_v1_host_plugin_service_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OnCreateSetRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_plugin_v1_host_plugin_service_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OnCreateSetResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_plugin_v1_host_plugin_service_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OnUpdateSetRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_plugin_v1_host_plugin_service_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OnUpdateSetResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_plugin_v1_host_plugin_service_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OnDeleteSetRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_plugin_v1_host_plugin_service_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OnDeleteSetResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_plugin_v1_host_plugin_service_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListHostsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_plugin_v1_host_plugin_service_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListHostsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_plugin_v1_host_plugin_service_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListHostsResponseHost); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_plugin_v1_host_plugin_service_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HostCatalogPersisted); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_plugin_v1_host_plugin_service_proto_rawDesc, + NumEnums: 0, + NumMessages: 16, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_plugin_v1_host_plugin_service_proto_goTypes, + DependencyIndexes: file_plugin_v1_host_plugin_service_proto_depIdxs, + MessageInfos: file_plugin_v1_host_plugin_service_proto_msgTypes, + }.Build() + File_plugin_v1_host_plugin_service_proto = out.File + file_plugin_v1_host_plugin_service_proto_rawDesc = nil + file_plugin_v1_host_plugin_service_proto_goTypes = nil + file_plugin_v1_host_plugin_service_proto_depIdxs = nil +} diff --git a/sdk/pbs/plugin/host_plugin_service_grpc.pb.go b/sdk/pbs/plugin/host_plugin_service_grpc.pb.go new file mode 100644 index 000000000..0819b6df4 --- /dev/null +++ b/sdk/pbs/plugin/host_plugin_service_grpc.pb.go @@ -0,0 +1,337 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package plugin + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// HostPluginServiceClient is the client API for HostPluginService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type HostPluginServiceClient interface { + // OnCreateCatalog is a hook that runs when a host catalog is + // created. + OnCreateCatalog(ctx context.Context, in *OnCreateCatalogRequest, opts ...grpc.CallOption) (*OnCreateCatalogResponse, error) + // OnUpdateCatalog is a hook that runs when a host catalog is + // updated. + OnUpdateCatalog(ctx context.Context, in *OnUpdateCatalogRequest, opts ...grpc.CallOption) (*OnUpdateCatalogResponse, error) + // OnDeleteCatalog is a hook that runs when a host catalog is + // deleted. + OnDeleteCatalog(ctx context.Context, in *OnDeleteCatalogRequest, opts ...grpc.CallOption) (*OnDeleteCatalogResponse, error) + // OnCreateSet is a hook that runs when a host set is created. + OnCreateSet(ctx context.Context, in *OnCreateSetRequest, opts ...grpc.CallOption) (*OnCreateSetResponse, error) + // OnUpdateSet is a hook that runs when a host set is updated. + OnUpdateSet(ctx context.Context, in *OnUpdateSetRequest, opts ...grpc.CallOption) (*OnUpdateSetResponse, error) + // OnDeleteSet is a hook that runs when a host set is deleted. + OnDeleteSet(ctx context.Context, in *OnDeleteSetRequest, opts ...grpc.CallOption) (*OnDeleteSetResponse, error) + // ListHosts looks up all the hosts in the provided host sets. + ListHosts(ctx context.Context, in *ListHostsRequest, opts ...grpc.CallOption) (*ListHostsResponse, error) +} + +type hostPluginServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewHostPluginServiceClient(cc grpc.ClientConnInterface) HostPluginServiceClient { + return &hostPluginServiceClient{cc} +} + +func (c *hostPluginServiceClient) OnCreateCatalog(ctx context.Context, in *OnCreateCatalogRequest, opts ...grpc.CallOption) (*OnCreateCatalogResponse, error) { + out := new(OnCreateCatalogResponse) + err := c.cc.Invoke(ctx, "/plugin.v1.HostPluginService/OnCreateCatalog", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *hostPluginServiceClient) OnUpdateCatalog(ctx context.Context, in *OnUpdateCatalogRequest, opts ...grpc.CallOption) (*OnUpdateCatalogResponse, error) { + out := new(OnUpdateCatalogResponse) + err := c.cc.Invoke(ctx, "/plugin.v1.HostPluginService/OnUpdateCatalog", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *hostPluginServiceClient) OnDeleteCatalog(ctx context.Context, in *OnDeleteCatalogRequest, opts ...grpc.CallOption) (*OnDeleteCatalogResponse, error) { + out := new(OnDeleteCatalogResponse) + err := c.cc.Invoke(ctx, "/plugin.v1.HostPluginService/OnDeleteCatalog", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *hostPluginServiceClient) OnCreateSet(ctx context.Context, in *OnCreateSetRequest, opts ...grpc.CallOption) (*OnCreateSetResponse, error) { + out := new(OnCreateSetResponse) + err := c.cc.Invoke(ctx, "/plugin.v1.HostPluginService/OnCreateSet", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *hostPluginServiceClient) OnUpdateSet(ctx context.Context, in *OnUpdateSetRequest, opts ...grpc.CallOption) (*OnUpdateSetResponse, error) { + out := new(OnUpdateSetResponse) + err := c.cc.Invoke(ctx, "/plugin.v1.HostPluginService/OnUpdateSet", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *hostPluginServiceClient) OnDeleteSet(ctx context.Context, in *OnDeleteSetRequest, opts ...grpc.CallOption) (*OnDeleteSetResponse, error) { + out := new(OnDeleteSetResponse) + err := c.cc.Invoke(ctx, "/plugin.v1.HostPluginService/OnDeleteSet", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *hostPluginServiceClient) ListHosts(ctx context.Context, in *ListHostsRequest, opts ...grpc.CallOption) (*ListHostsResponse, error) { + out := new(ListHostsResponse) + err := c.cc.Invoke(ctx, "/plugin.v1.HostPluginService/ListHosts", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// HostPluginServiceServer is the server API for HostPluginService service. +// All implementations must embed UnimplementedHostPluginServiceServer +// for forward compatibility +type HostPluginServiceServer interface { + // OnCreateCatalog is a hook that runs when a host catalog is + // created. + OnCreateCatalog(context.Context, *OnCreateCatalogRequest) (*OnCreateCatalogResponse, error) + // OnUpdateCatalog is a hook that runs when a host catalog is + // updated. + OnUpdateCatalog(context.Context, *OnUpdateCatalogRequest) (*OnUpdateCatalogResponse, error) + // OnDeleteCatalog is a hook that runs when a host catalog is + // deleted. + OnDeleteCatalog(context.Context, *OnDeleteCatalogRequest) (*OnDeleteCatalogResponse, error) + // OnCreateSet is a hook that runs when a host set is created. + OnCreateSet(context.Context, *OnCreateSetRequest) (*OnCreateSetResponse, error) + // OnUpdateSet is a hook that runs when a host set is updated. + OnUpdateSet(context.Context, *OnUpdateSetRequest) (*OnUpdateSetResponse, error) + // OnDeleteSet is a hook that runs when a host set is deleted. + OnDeleteSet(context.Context, *OnDeleteSetRequest) (*OnDeleteSetResponse, error) + // ListHosts looks up all the hosts in the provided host sets. + ListHosts(context.Context, *ListHostsRequest) (*ListHostsResponse, error) + mustEmbedUnimplementedHostPluginServiceServer() +} + +// UnimplementedHostPluginServiceServer must be embedded to have forward compatible implementations. +type UnimplementedHostPluginServiceServer struct { +} + +func (UnimplementedHostPluginServiceServer) OnCreateCatalog(context.Context, *OnCreateCatalogRequest) (*OnCreateCatalogResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method OnCreateCatalog not implemented") +} +func (UnimplementedHostPluginServiceServer) OnUpdateCatalog(context.Context, *OnUpdateCatalogRequest) (*OnUpdateCatalogResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method OnUpdateCatalog not implemented") +} +func (UnimplementedHostPluginServiceServer) OnDeleteCatalog(context.Context, *OnDeleteCatalogRequest) (*OnDeleteCatalogResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method OnDeleteCatalog not implemented") +} +func (UnimplementedHostPluginServiceServer) OnCreateSet(context.Context, *OnCreateSetRequest) (*OnCreateSetResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method OnCreateSet not implemented") +} +func (UnimplementedHostPluginServiceServer) OnUpdateSet(context.Context, *OnUpdateSetRequest) (*OnUpdateSetResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method OnUpdateSet not implemented") +} +func (UnimplementedHostPluginServiceServer) OnDeleteSet(context.Context, *OnDeleteSetRequest) (*OnDeleteSetResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method OnDeleteSet not implemented") +} +func (UnimplementedHostPluginServiceServer) ListHosts(context.Context, *ListHostsRequest) (*ListHostsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListHosts not implemented") +} +func (UnimplementedHostPluginServiceServer) mustEmbedUnimplementedHostPluginServiceServer() {} + +// UnsafeHostPluginServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to HostPluginServiceServer will +// result in compilation errors. +type UnsafeHostPluginServiceServer interface { + mustEmbedUnimplementedHostPluginServiceServer() +} + +func RegisterHostPluginServiceServer(s grpc.ServiceRegistrar, srv HostPluginServiceServer) { + s.RegisterService(&HostPluginService_ServiceDesc, srv) +} + +func _HostPluginService_OnCreateCatalog_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(OnCreateCatalogRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HostPluginServiceServer).OnCreateCatalog(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/plugin.v1.HostPluginService/OnCreateCatalog", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HostPluginServiceServer).OnCreateCatalog(ctx, req.(*OnCreateCatalogRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HostPluginService_OnUpdateCatalog_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(OnUpdateCatalogRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HostPluginServiceServer).OnUpdateCatalog(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/plugin.v1.HostPluginService/OnUpdateCatalog", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HostPluginServiceServer).OnUpdateCatalog(ctx, req.(*OnUpdateCatalogRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HostPluginService_OnDeleteCatalog_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(OnDeleteCatalogRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HostPluginServiceServer).OnDeleteCatalog(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/plugin.v1.HostPluginService/OnDeleteCatalog", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HostPluginServiceServer).OnDeleteCatalog(ctx, req.(*OnDeleteCatalogRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HostPluginService_OnCreateSet_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(OnCreateSetRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HostPluginServiceServer).OnCreateSet(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/plugin.v1.HostPluginService/OnCreateSet", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HostPluginServiceServer).OnCreateSet(ctx, req.(*OnCreateSetRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HostPluginService_OnUpdateSet_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(OnUpdateSetRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HostPluginServiceServer).OnUpdateSet(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/plugin.v1.HostPluginService/OnUpdateSet", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HostPluginServiceServer).OnUpdateSet(ctx, req.(*OnUpdateSetRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HostPluginService_OnDeleteSet_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(OnDeleteSetRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HostPluginServiceServer).OnDeleteSet(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/plugin.v1.HostPluginService/OnDeleteSet", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HostPluginServiceServer).OnDeleteSet(ctx, req.(*OnDeleteSetRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HostPluginService_ListHosts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListHostsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HostPluginServiceServer).ListHosts(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/plugin.v1.HostPluginService/ListHosts", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HostPluginServiceServer).ListHosts(ctx, req.(*ListHostsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// HostPluginService_ServiceDesc is the grpc.ServiceDesc for HostPluginService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var HostPluginService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "plugin.v1.HostPluginService", + HandlerType: (*HostPluginServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "OnCreateCatalog", + Handler: _HostPluginService_OnCreateCatalog_Handler, + }, + { + MethodName: "OnUpdateCatalog", + Handler: _HostPluginService_OnUpdateCatalog_Handler, + }, + { + MethodName: "OnDeleteCatalog", + Handler: _HostPluginService_OnDeleteCatalog_Handler, + }, + { + MethodName: "OnCreateSet", + Handler: _HostPluginService_OnCreateSet_Handler, + }, + { + MethodName: "OnUpdateSet", + Handler: _HostPluginService_OnUpdateSet_Handler, + }, + { + MethodName: "OnDeleteSet", + Handler: _HostPluginService_OnDeleteSet_Handler, + }, + { + MethodName: "ListHosts", + Handler: _HostPluginService_ListHosts_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "plugin/v1/host_plugin_service.proto", +} diff --git a/sdk/plugins/host/load.go b/sdk/plugins/host/load.go new file mode 100644 index 000000000..fa50c2bb9 --- /dev/null +++ b/sdk/plugins/host/load.go @@ -0,0 +1,213 @@ +package external_host_plugins + +import ( + "bytes" + "compress/gzip" + "context" + "fmt" + "io/fs" + "io/ioutil" + "os" + "path/filepath" + "runtime" + "strings" + + pb "github.com/hashicorp/boundary/sdk/pbs/plugin" +) + +// NOTE: This package could probably use some reflect based bits to allow +// loading other types of plugins later. That is an exercise left for future +// refactoring, but worth calling out now. + +type pluginInfo struct { + containerFs fs.FS + filename string + creationFunc func() (pb.HostPluginServiceClient, error) +} + +// CreateHostPlugin takes in a type, parses the various options to look for a +// plugin matching that name, and returns a host plugin client, a cleanup +// function to execute on shutdown of the enclosing program, and an error. +func CreateHostPlugin( + ctx context.Context, + pluginType string, + opt ...Option, +) ( + hp pb.HostPluginServiceClient, + cleanup func() error, + retErr error, +) { + defer func() { + if retErr != nil && cleanup != nil { + _ = cleanup() + } + }() + + pluginType = strings.ToLower(pluginType) + + opts, err := getOpts(opt...) + if err != nil { + return nil, nil, fmt.Errorf("error parsing host plugin options: %w", err) + } + if len(opts.withHostPluginsSources) == 0 { + return nil, nil, fmt.Errorf("no host plugins available") + } + + // First, scan available plugins, then find the right one to use + pluginMap := map[string]pluginInfo{} + var plugin pluginInfo + { + for _, sourceInfo := range opts.withHostPluginsSources { + switch { + case sourceInfo.pluginFs != nil: + dirs, err := fs.ReadDir(sourceInfo.pluginFs, ".") + if err != nil { + return nil, nil, fmt.Errorf("error scanning host plugins: %w", err) + } + // Store a match between the config type string and the expected plugin name + for _, entry := range dirs { + pluginType := strings.TrimSuffix(strings.TrimPrefix(entry.Name(), sourceInfo.pluginFsPrefix), ".gz") + if runtime.GOOS == "windows" { + pluginType = strings.TrimSuffix(pluginType, ".exe") + } + pluginMap[pluginType] = pluginInfo{ + containerFs: sourceInfo.pluginFs, + filename: entry.Name(), + } + } + case sourceInfo.pluginMap != nil: + for k, creationFunc := range sourceInfo.pluginMap { + pluginMap[k] = pluginInfo{creationFunc: creationFunc, filename: k} + } + } + } + + plugin = pluginMap[pluginType] + if plugin.filename == "" && plugin.creationFunc == nil { + return nil, nil, fmt.Errorf("unknown host plugin type %q", pluginType) + } + } + + // If the source is just a func, execute it and skip ahead; otherwise it's a plugin, so instantiate it + { + switch { + case plugin.creationFunc != nil: + hp, err = plugin.creationFunc() + if err != nil { + return nil, nil, fmt.Errorf("error performing direct instantiation of host plugin with type %q: %w", plugin.filename, err) + } + + case plugin.containerFs != nil: + hp, cleanup, err = executeHostPlugin(plugin, opt...) + if err != nil { + return nil, cleanup, err + } + } + } + + return hp, cleanup, nil +} + +// executeHostPlugin takes in the discovered plugin information and spins out +// the actual binary, returning a client that talks to it. +func executeHostPlugin(plugin pluginInfo, opt ...Option) (pb.HostPluginServiceClient, func() error, error) { + opts, err := getOpts(opt...) + if err != nil { + return nil, nil, fmt.Errorf("error parsing host plugin options: %w", err) + } + + // Open and basic validation + file, err := plugin.containerFs.Open(plugin.filename) + if err != nil { + return nil, nil, err + } + stat, err := file.Stat() + if err != nil { + return nil, nil, fmt.Errorf("error discovering host plugin information: %w", err) + } + if stat.IsDir() { + return nil, nil, fmt.Errorf("host plugin is a directory, not a file") + } + + // Read in plugin bytes + expLen := stat.Size() + buf := make([]byte, expLen) + readLen, err := file.Read(buf) + if err != nil { + file.Close() + return nil, nil, fmt.Errorf("error reading host plugin bytes: %w", err) + } + if err := file.Close(); err != nil { + return nil, nil, fmt.Errorf("error closing host plugin file: %w", err) + } + if int64(readLen) != expLen { + return nil, nil, fmt.Errorf("reading host plugin expected %d bytes, read %d", expLen, readLen) + } + + executedFileName := plugin.filename + + // If it's compressed, uncompress it + if strings.HasSuffix(plugin.filename, ".gz") { + executedFileName = strings.TrimSuffix(plugin.filename, ".gz") + gzipReader, err := gzip.NewReader(bytes.NewReader(buf)) + if err != nil { + return nil, nil, fmt.Errorf("error creating gzip decompression reader: %w", err) + } + uncompBuf := new(bytes.Buffer) + _, err = uncompBuf.ReadFrom(gzipReader) + gzipReader.Close() + if err != nil { + return nil, nil, fmt.Errorf("error reading gzip compressed data from reader: %w", err) + } + buf = uncompBuf.Bytes() + } + + cleanup := func() error { + return nil + } + + // Now, create a temp dir and write out the plugin bytes + dir := opts.withHostPluginExecutionDir + if dir == "" { + tmpDir, err := ioutil.TempDir("", "*") + if err != nil { + return nil, nil, fmt.Errorf("error creating tmp dir for kms execution: %w", err) + } + cleanup = func() error { + return os.RemoveAll(tmpDir) + } + dir = tmpDir + } + pluginPath := filepath.Join(dir, executedFileName) + if err := ioutil.WriteFile(pluginPath, buf, fs.FileMode(0o700)); err != nil { + return nil, cleanup, fmt.Errorf("error writing out host plugin for execution: %w", err) + } + + // Execute the plugin + client, err := NewHostPluginClient(pluginPath, WithLogger(opts.withLogger)) + if err != nil { + return nil, cleanup, fmt.Errorf("error fetching host plugin client: %w", err) + } + origCleanup := cleanup + cleanup = func() error { + client.Kill() + return origCleanup() + } + rpcClient, err := client.Client() + if err != nil { + return nil, cleanup, fmt.Errorf("error fetching host plugin rpc client: %w", err) + } + + raw, err := rpcClient.Dispense(hostServicePluginSetName) + if err != nil { + return nil, cleanup, fmt.Errorf("error dispensing host plugin: %w", err) + } + + var ok bool + hp, ok := raw.(pb.HostPluginServiceClient) + if !ok { + return nil, cleanup, fmt.Errorf("error converting rpc host plugin to host plugin interface: %w", err) + } + + return hp, cleanup, nil +} diff --git a/sdk/plugins/host/options.go b/sdk/plugins/host/options.go new file mode 100644 index 000000000..2046c2355 --- /dev/null +++ b/sdk/plugins/host/options.go @@ -0,0 +1,119 @@ +package external_host_plugins + +import ( + "errors" + "fmt" + "io/fs" + "os" + + pb "github.com/hashicorp/boundary/sdk/pbs/plugin" + "github.com/hashicorp/go-hclog" +) + +// getOpts iterates the inbound Options and returns a struct +func getOpts(opt ...Option) (*options, error) { + opts := getDefaultOptions() + for _, o := range opt { + if o == nil { + continue + } + if err := o(opts); err != nil { + return nil, fmt.Errorf("error running option function: %w", err) + } + } + return opts, nil +} + +// Option - a type that wraps an interface for compile-time safety but can +// contain an option for this package or for wrappers implementing this +// interface. +type Option func(*options) error + +type options struct { + withHostPluginsSources []pluginSourceInfo + withHostPluginExecutionDir string + withLogger hclog.Logger +} + +func getDefaultOptions() *options { + return &options{} +} + +// pluginSourceInfo contains information about plugin sources. Each entry in the +// final slice of withHostPluginsSources will either have the Fs/FsPrefix values +// populated or have the map populated. The former is for loading and executing +// a plugin via go-plugin from the filesystem; the latter is for executing it +// directly in-memory. +type pluginSourceInfo struct { + pluginMap map[string]func() (pb.HostPluginServiceClient, error) + pluginFs fs.FS + pluginFsPrefix string +} + +// WithLogger allows passing a logger to the plugin library for debugging +func WithLogger(logger hclog.Logger) Option { + return func(o *options) error { + o.withLogger = logger + return nil + } +} + +// WithHostPluginsFilesystem provides an fs.FS containing plugins that can be +// executed to provide Host functionality. This can be specified multiple times; +// all FSes will be scanned. If there are conflicts, the last one wins (this +// property is shared with WithHostPluginsMap). The prefix will be stripped from +// each entry when determining the plugin type. +func WithHostPluginsFilesystem(prefix string, plugins fs.FS) Option { + return func(o *options) error { + if plugins == nil { + return errors.New("nil host plugin filesystem passed into option") + } + o.withHostPluginsSources = append(o.withHostPluginsSources, + pluginSourceInfo{ + pluginFs: plugins, + pluginFsPrefix: prefix, + }, + ) + return nil + } +} + +// WithHostPluginsMap provides a map containing functions that can be called to +// provide implementations of the server. This can be specified multiple times; +// all FSes will be scanned. If there are conflicts, the last one wins (this +// property is shared with WithHostPluginsFilesystem). +func WithHostPluginsMap(plugins map[string]func() (pb.HostPluginServiceClient, error)) Option { + return func(o *options) error { + if len(plugins) == 0 { + return errors.New("no entries in host plugins map passed into option") + } + o.withHostPluginsSources = append(o.withHostPluginsSources, + pluginSourceInfo{ + pluginMap: plugins, + }, + ) + return nil + } +} + +// WithHostPluginExecutionDir allows setting a specific directory for +// writing out and executing plugins; if not set, os.TempDir will be used +// to create a suitable directory. +func WithHostPluginExecutionDir(dir string) Option { + return func(o *options) error { + if dir == "" { + // We always call this with the option, so if it's not actually set, + // don't error + return nil + } + fi, err := os.Stat(dir) + if err != nil { + return fmt.Errorf("error while performing stat to validate path %q is a directory: %w", dir, err) + } + if !fi.IsDir() { + return fmt.Errorf("given plugin execution path %q is not a directory", dir) + } + o.withHostPluginExecutionDir = dir + return nil + } +} diff --git a/sdk/plugins/host/plugin.go b/sdk/plugins/host/plugin.go new file mode 100644 index 000000000..6b44db321 --- /dev/null +++ b/sdk/plugins/host/plugin.go @@ -0,0 +1,89 @@ +package external_host_plugins + +import ( + "context" + "fmt" + "os/exec" + + pb "github.com/hashicorp/boundary/sdk/pbs/plugin" + "github.com/hashicorp/go-plugin" + "google.golang.org/grpc" +) + +const ( + hostServicePluginSetName = "host-plugin" +) + +// HandshakeConfig is a shared config that can be used regardless of plugin, to +// avoid having to know type-specific things about each plugin +var HandshakeConfig = plugin.HandshakeConfig{ + MagicCookieKey: "HASHICORP_BOUNDARY_HOST_PLUGIN", + MagicCookieValue: hostServicePluginSetName, +} + +// ServeHostPlugin is a generic function to start serving a host plugin service as a +// plugin +func ServeHostPlugin(svc pb.HostPluginServiceServer, opt ...Option) error { + opts, err := getOpts(opt...) + if err != nil { + return err + } + hostServiceServer, err := NewHostPluginServiceServer(svc) + if err != nil { + return err + } + plugin.Serve(&plugin.ServeConfig{ + HandshakeConfig: HandshakeConfig, + VersionedPlugins: map[int]plugin.PluginSet{ + 1: {hostServicePluginSetName: hostServiceServer}, + }, + Logger: opts.withLogger, + GRPCServer: plugin.DefaultGRPCServer, + }) + return nil +} + +type hostPlugin struct { + plugin.Plugin + + impl pb.HostPluginServiceServer +} + +func NewHostPluginServiceServer(impl pb.HostPluginServiceServer) (*hostPlugin, error) { + if impl == nil { + return nil, fmt.Errorf("empty underlying host plugin passed in") + } + return &hostPlugin{ + impl: impl, + }, nil +} + +func NewHostPluginClient(pluginPath string, opt ...Option) (*plugin.Client, error) { + opts, err := getOpts(opt...) + if err != nil { + return nil, err + } + hostServiceClient := &hostPlugin{} + + return plugin.NewClient(&plugin.ClientConfig{ + HandshakeConfig: HandshakeConfig, + VersionedPlugins: map[int]plugin.PluginSet{ + 1: {hostServicePluginSetName: hostServiceClient}, + }, + Cmd: exec.Command(pluginPath), + AllowedProtocols: []plugin.Protocol{ + plugin.ProtocolGRPC, + }, + Logger: opts.withLogger, + AutoMTLS: true, + }), nil +} + +func (h *hostPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error { + pb.RegisterHostPluginServiceServer(s, h.impl) + return nil +} + +func (h *hostPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) { + return pb.NewHostPluginServiceClient(c), nil +}