Merge branch 'plugin-hostcatalogs'

This commit is contained in:
Todd Knight committed 2021-11-12 10:20:11 -07:00
commit 228be58cc8
191 files changed
+27664 -1348

No files matched your search

+146 -67
View File
@@ -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
+141 -67
View File
@@ -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
+1
View File
@@ -120,6 +120,7 @@ main
/bin/
update-ui-assets*
/plugins/kms/assets/
/plugins/host/assets/
# Test config file
test*.hcl
+6 -5
View File
@@ -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:
+1 -1
View File
@@ -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)
+19 -2
View File
@@ -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
)
+4
View File
@@ -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"`
+25
View File
@@ -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
}
}
+4
View File
@@ -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
+16 -12
View File
@@ -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
}
+36
View File
@@ -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
}
}
+8
View File
@@ -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"`
}
+7
View File
@@ -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"
)
+137 -9
View File
@@ -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
)
+62 -26
View File
@@ -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=
+26 -5
View File
@@ -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",
+5 -5
View File
@@ -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
+4 -19
View File
@@ -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
}
+13 -3
View File
@@ -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.
+12
View File
@@ -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 " <not included in response>"
}
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
+63
View File
@@ -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
}
+12
View File
@@ -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
}
}
}
+4
View File
@@ -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
+24
View File
@@ -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),
+40
View File
@@ -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 = <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})
+60 -43
View File
@@ -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 {
+24 -8
View File
@@ -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)
}
@@ -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()
}
@@ -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 }
)
@@ -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"`,
"",
"",
})
+1 -1
View File
@@ -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
+27 -8
View File
@@ -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)
}
@@ -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:<valid IPv4/6 CIDR>" ` +
`or "dns:<globbed name>", 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
}
@@ -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 }
)
+13
View File
@@ -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) {
+13
View File
@@ -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) {
+32
View File
@@ -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": {
{
+11
View File
@@ -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
+10 -1
View File
@@ -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{
+15 -9
View File
@@ -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
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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)
})
}
}
@@ -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;
@@ -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;
+4
View File
@@ -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
+5
View File
@@ -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) {
+4
View File
@@ -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,
},
}
+78
View File
@@ -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": {
@@ -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 (
@@ -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
+9
View File
@@ -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
}
+25
View File
@@ -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
}
+72
View File
@@ -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
}
+77
View File
@@ -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
}
+46
View File
@@ -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
}
}
+34
View File
@@ -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)
})
}
+48
View File
@@ -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)
}
+161
View File
@@ -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
}
+418
View File
@@ -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)
}
})
}
}
+151
View File
@@ -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
}
+130
View File
@@ -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
}
@@ -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))
}
+328
View File
@@ -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())
})
}
}
+184
View File
@@ -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"
}
+60
View File
@@ -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
}
@@ -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())
})
}
}
+224
View File
@@ -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())
})
}
}
+204
View File
@@ -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())
})
}
}
+56
View File
@@ -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
}
@@ -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: &timestamppb.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: &timestamppb.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),
}
}
@@ -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
}
@@ -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))
}
+559
View File
@@ -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
}
+483
View File
@@ -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))
})
}
}
+32
View File
@@ -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
}
+150
View File
@@ -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
}
+362
View File
@@ -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)
})
}
}
+124
View File
@@ -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
}
}
+71
View File
@@ -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)
})
}
+46
View File
@@ -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
`
)
+71
View File
@@ -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
}
+118
View File
@@ -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
}
@@ -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
}
File diff suppressed because it is too large. Load diff
+822
View File
@@ -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
}
File diff suppressed because it is too large. Load diff
@@ -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(&timestamp.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(&timestamp.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(&timestamp.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(&timestamp.Timestamp{}),
),
)
assert.Empty(
cmp.Diff(
plg,
gotPlg,
cmpopts.IgnoreUnexported(hostplg.Plugin{}, hostplgstore.Plugin{}),
cmpopts.IgnoreTypes(&timestamp.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)
}
})
}
}
@@ -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
}
@@ -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)
}
})
}
}
+166
View File
@@ -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)
})
}
}
+895
View File
@@ -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
}
+292
View File
@@ -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)
}
+142
View File
@@ -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()})
}
+87
View File
@@ -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
}
Loaded 100 of 191 files, more files were not shown because too many files have changed in this diff. Show more