Merge branch 'plugin-hostcatalogs'
This commit is contained in:
commit
228be58cc8
191 files changed
+27664
-1348
No files matched your search
Generated
+146
-67
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -120,6 +120,7 @@ main
|
||||
/bin/
|
||||
update-ui-assets*
|
||||
/plugins/kms/assets/
|
||||
/plugins/host/assets/
|
||||
|
||||
# Test config file
|
||||
test*.hcl
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
|
||||
)
|
||||
@@ -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"`
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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"
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
@@ -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=
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"`,
|
||||
"",
|
||||
"",
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
)
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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": {
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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: ×tamppb.Timestamp{Seconds: 0, Nanos: 0}}
|
||||
o, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
plg := host.TestPlugin(t, conn, "test")
|
||||
plg2 := host.TestPlugin(t, conn, "test2")
|
||||
new := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId())
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
update *HostCatalog
|
||||
fieldMask []string
|
||||
}{
|
||||
{
|
||||
name: "public_id",
|
||||
update: func() *HostCatalog {
|
||||
c := new.clone()
|
||||
c.PublicId = "hc_thisIsNotAValidId"
|
||||
return c
|
||||
}(),
|
||||
fieldMask: []string{"PublicId"},
|
||||
},
|
||||
{
|
||||
name: "create time",
|
||||
update: func() *HostCatalog {
|
||||
c := new.clone()
|
||||
c.CreateTime = &ts
|
||||
return c
|
||||
}(),
|
||||
fieldMask: []string{"CreateTime"},
|
||||
},
|
||||
{
|
||||
name: "scope id",
|
||||
update: func() *HostCatalog {
|
||||
c := new.clone()
|
||||
c.ScopeId = o.PublicId
|
||||
return c
|
||||
}(),
|
||||
fieldMask: []string{"ScopeId"},
|
||||
},
|
||||
{
|
||||
name: "plugin id",
|
||||
update: func() *HostCatalog {
|
||||
c := new.clone()
|
||||
c.PluginId = plg2.GetPublicId()
|
||||
return c
|
||||
}(),
|
||||
fieldMask: []string{"PluginId"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
orig := new.clone()
|
||||
err := w.LookupById(context.Background(), orig)
|
||||
require.NoError(err)
|
||||
|
||||
rowsUpdated, err := w.Update(context.Background(), tt.update, tt.fieldMask, nil, db.WithSkipVetForWrite(true))
|
||||
require.Error(err)
|
||||
assert.Equal(0, rowsUpdated)
|
||||
|
||||
after := new.clone()
|
||||
err = w.LookupById(context.Background(), after)
|
||||
require.NoError(err)
|
||||
|
||||
assert.True(proto.Equal(orig, after))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluginSet_ImmutableFields(t *testing.T) {
|
||||
t.Parallel()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
w := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
sched := scheduler.TestScheduler(t, conn, wrapper)
|
||||
|
||||
ts := timestamp.Timestamp{Timestamp: ×tamppb.Timestamp{Seconds: 0, Nanos: 0}}
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
plg := host.TestPlugin(t, conn, "test")
|
||||
cat := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId())
|
||||
new := TestSet(t, conn, kmsCache, sched, cat, map[string]plgpb.HostPluginServiceClient{
|
||||
plg.GetPublicId(): NewWrappingPluginClient(&TestPluginServer{}),
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
update *HostSet
|
||||
fieldMask []string
|
||||
}{
|
||||
{
|
||||
name: "public_id",
|
||||
update: func() *HostSet {
|
||||
c := new.testCloneHostSet()
|
||||
c.PublicId = "hc_thisIsNotAValidId"
|
||||
return c
|
||||
}(),
|
||||
fieldMask: []string{"PublicId"},
|
||||
},
|
||||
{
|
||||
name: "create time",
|
||||
update: func() *HostSet {
|
||||
c := new.testCloneHostSet()
|
||||
c.CreateTime = &ts
|
||||
return c
|
||||
}(),
|
||||
fieldMask: []string{"CreateTime"},
|
||||
},
|
||||
{
|
||||
name: "host_plugin_catalog_id",
|
||||
update: func() *HostSet {
|
||||
c := new.testCloneHostSet()
|
||||
c.CatalogId = "stc_01234567890"
|
||||
return c
|
||||
}(),
|
||||
fieldMask: []string{"CatalogId"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
orig := new.testCloneHostSet()
|
||||
err := w.LookupById(context.Background(), orig)
|
||||
require.NoError(err)
|
||||
|
||||
rowsUpdated, err := w.Update(context.Background(), tt.update, tt.fieldMask, nil, db.WithSkipVetForWrite(true))
|
||||
require.Error(err)
|
||||
assert.Equal(0, rowsUpdated)
|
||||
|
||||
after := new.testCloneHostSet()
|
||||
err = w.LookupById(context.Background(), after)
|
||||
require.NoError(err)
|
||||
|
||||
assert.True(proto.Equal(orig, after))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (c *HostSet) testCloneHostSet() *HostSet {
|
||||
cp := proto.Clone(c.HostSet)
|
||||
return &HostSet{
|
||||
HostSet: cp.(*store.HostSet),
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
`
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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(×tamp.Timestamp{}),
|
||||
cmpopts.SortSlices(func(x, y *Host) bool {
|
||||
return x.GetPublicId() < y.GetPublicId()
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
repo, err := NewRepository(rw, rw, kms, sched, plgm)
|
||||
require.NoError(err)
|
||||
require.NotNil(repo)
|
||||
|
||||
// Check again, but via performing an explicit list
|
||||
var gotPlg *hostplg.Plugin
|
||||
got, gotPlg, err = repo.ListHostsByCatalogId(ctx, in.catalog.GetPublicId())
|
||||
require.NoError(err)
|
||||
require.NotNil(got)
|
||||
assert.Len(got, len(in.phs))
|
||||
assert.Empty(
|
||||
cmp.Diff(
|
||||
in.exp,
|
||||
got,
|
||||
cmpopts.IgnoreUnexported(Host{}, store.Host{}),
|
||||
cmpopts.IgnoreTypes(×tamp.Timestamp{}),
|
||||
cmpopts.SortSlices(func(x, y *Host) bool {
|
||||
return x.GetPublicId() < y.GetPublicId()
|
||||
}),
|
||||
),
|
||||
)
|
||||
assert.Empty(
|
||||
cmp.Diff(
|
||||
plg,
|
||||
gotPlg,
|
||||
cmpopts.IgnoreUnexported(hostplg.Plugin{}, hostplgstore.Plugin{}),
|
||||
cmpopts.IgnoreTypes(×tamp.Timestamp{}),
|
||||
),
|
||||
)
|
||||
|
||||
// Now individually call read on each host, cache the matching set
|
||||
// IDs, and then check membership
|
||||
setIdMap := make(map[string][]string)
|
||||
for _, exp := range in.exp {
|
||||
for _, setId := range exp.SetIds {
|
||||
setIdMap[setId] = append(setIdMap[setId], exp.GetPublicId())
|
||||
}
|
||||
got, gotPlg, err := repo.LookupHost(ctx, exp.GetPublicId())
|
||||
require.NoError(err)
|
||||
require.NotNil(got)
|
||||
assert.NotEmpty(got.SetIds)
|
||||
assert.Empty(
|
||||
cmp.Diff(
|
||||
exp,
|
||||
got,
|
||||
cmpopts.IgnoreUnexported(Host{}, store.Host{}),
|
||||
cmpopts.IgnoreTypes(×tamp.Timestamp{}),
|
||||
),
|
||||
)
|
||||
assert.Empty(
|
||||
cmp.Diff(
|
||||
plg,
|
||||
gotPlg,
|
||||
cmpopts.IgnoreUnexported(hostplg.Plugin{}, hostplgstore.Plugin{}),
|
||||
cmpopts.IgnoreTypes(×tamp.Timestamp{}),
|
||||
),
|
||||
)
|
||||
}
|
||||
for setId, expHostIds := range setIdMap {
|
||||
got, err = repo.ListHostsBySetIds(ctx, []string{setId})
|
||||
require.NoError(err)
|
||||
require.NotNil(got)
|
||||
var gotHostIds []string
|
||||
for _, h := range got {
|
||||
gotHostIds = append(gotHostIds, h.GetPublicId())
|
||||
}
|
||||
assert.ElementsMatch(expHostIds, gotHostIds)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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()})
|
||||
}
|
||||
@@ -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
Reference in new issue
Block a user