feat(host): Add host plugin related schemas and tests (#1475)

* feat(host): Add host plugin related schemas and tests

This adds the schema files for host plugins, plugin based catalogs and
plugin based sets.  This also adds the protos and tests which utilize
gorm to ensure the schemas enforce the constraints we expect.

Along with this change was a migration which moved catalog names to the
base type table so we can ensure all catalogs in a single scope,
regardless of subtype, have a unique name.

* Updated plugin schemas to do the following:
- Add plugin_executable table
- plugin table now also has scope_id and name w/ uniqueness constraints
- host_plugin no longer has id_prefix column
- host_plugin separates name and plugin_name
- plugin_version table renamed version to symantic_version column.

* Fix typo in delete_plugin_subtype trigger.  Add update test for host plugins.

* Update id prefixes. Fix public_id typos in protos and plugin_executable name in sql diagrams.

* Created internal/plugin and moved base plugin structs to it and moved host plugin subdirectory to it.

* Created internal/plugin and moved base plugin structs to it and moved host plugin subdirectory to it.

* Add first pass at enums for OS and Arch supported by plugin executables.

* Capitalizing the const enums for OS and Architectures.

* Add comments to sql files.

* Reviewer comments.

Remove unsupported OSes and Archs from SQL and go constants.
Add missing tests.

* Be more permissive in the values the db accepts.

The go logic can restrict which values it will allow getting populated into the db.

* Removing constraints on which values can be placed inside operating_system and architecture columns.

* Updating test helper functions to only create a single catalog or set.

* Move files to new migration version.

* Fixing job/plugin test.

* Update table names, named a number of constraints, remove plugin version and executable tables.

* Add constraints for plugin name and id prefix.

* Rename constraint, remove unhelpful comment block.

* Adding semantic version to plugin_host and set attributes to not null.

* Remove semantic versioning from all locations.

* Remove semantic_version from plugin_host table comment.

* Fix op copy/paste error. Don't use deprecated errors methods. Update constraint names.  Add docs.

* Apply suggestions from code review

Fixing constraint names.

Co-authored-by: Michael Gaffney <mgaffney@users.noreply.github.com>
This commit is contained in:
Todd KnightandMichael Gaffney authored and GitHub committed 2021-09-08 10:06:38 -07:00
1 parent 71cba858e3
commit db9e1106a9
39 files changed
+3987 -4

No files matched your search

+3
View File
@@ -121,6 +121,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
@@ -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,89 @@
begin;
/*
┌──────────────────┐
│ plugin │
├──────────────────┤
│public_id (pk) │
│scope_id (fk) │
└──────────────────┘
┼
┼
│
┼
┼
┌──────────────────┐
│ plugin_host │
├──────────────────┤
│public_id (pk) │
│scope_id (fk) │
│name │
│description │
│version │
│plugin_name │
│id_prefix │
└──────────────────┘
*/
create table plugin_host (
public_id wt_plugin_id primary key,
scope_id wt_scope_id not null
-- TODO: Allow plugins to be created in different scopes and
-- constrain the host-catalog's plugin reference accordingly.
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,
plugin_name text not null
constraint plugin_name_must_be_not_empty
check(length(trim(plugin_name)) > 0)
constraint plugin_name_must_be_lowercase
check(lower(trim(plugin_name)) = plugin_name)
constraint plugin_host_plugin_name_uq
unique,
id_prefix text not null
constraint plugin_id_prefix_must_be_not_empty
check(length(trim(id_prefix)) > 0)
constraint plugin_id_prefix_must_fit_format
check (id_prefix ~ '^[a-z0-9]*$')
constraint plugin_host_id_prefix_uq
unique,
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', 'plugin_name');
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,167 @@
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 attributes_must_not_be_empty
check(length(attributes) > 0),
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,
version wt_version,
attributes bytea not null
constraint attributes_must_not_be_empty
check(length(attributes) > 0),
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();
insert into oplog_ticket (name, version)
values
('host_plugin_catalog', 1),
('host_plugin_catalog_secret', 1),
('host_plugin_set', 1);
commit;
+388 -1
View File
@@ -4,7 +4,7 @@ package schema
func init() {
migrationStates["postgres"] = migrationState{
binarySchemaVersion: 15002,
binarySchemaVersion: 16004,
upMigrations: map[int][]byte{
1: []byte(`
create domain wt_public_id as text
@@ -6938,6 +6938,393 @@ alter table wh_host_dimension
end;
$$ language plpgsql;
`),
16001: []byte(`
-- 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);
`),
16002: []byte(`
/*
┌──────────────────┐
│ plugin │
├──────────────────┤
│public_id (pk) │
│scope_id (fk) │
└──────────────────┘
┼
┼
│
┼
┼
┌──────────────────┐
│ plugin_host │
├──────────────────┤
│public_id (pk) │
│scope_id (fk) │
│name │
│description │
│version │
│plugin_name │
│id_prefix │
└──────────────────┘
*/
create table plugin_host (
public_id wt_plugin_id primary key,
scope_id wt_scope_id not null
-- TODO: Allow plugins to be created in different scopes and
-- constrain the host-catalog's plugin reference accordingly.
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,
plugin_name text not null
constraint plugin_name_must_be_not_empty
check(length(trim(plugin_name)) > 0)
constraint plugin_name_must_be_lowercase
check(lower(trim(plugin_name)) = plugin_name)
constraint plugin_host_name_uq
unique,
id_prefix text not null
constraint plugin_id_prefix_must_be_not_empty
check(length(trim(id_prefix)) > 0)
constraint plugin_id_prefix_must_fit_format
check (id_prefix ~ '^[a-z0-9]*$')
constraint plugin_host_id_prefix_uq
unique,
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', 'plugin_name');
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);
`),
16003: []byte(`
-- 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();
`),
16004: []byte(`
/*
┌──────────────────┐
│ 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 attributes_must_not_empty
check(length(attributes) > 0),
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,
version wt_version,
attributes bytea not null
constraint attributes_must_not_empty
check(length(attributes) > 0),
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();
insert into oplog_ticket (name, version)
values
('host_plugin_catalog', 1),
('host_plugin_catalog_secret', 1),
('host_plugin_set', 1);
`),
2001: []byte(`
-- log_migration entries represent logs generated during migrations
+66
View File
@@ -0,0 +1,66 @@
// 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"
"encoding/json"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/host/plugin/store"
"google.golang.org/protobuf/proto"
)
// A HostCatalog contains plugin host sets. It is owned by
// a scope.
type HostCatalog struct {
*store.HostCatalog
tableName string `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, pluginId, scopeId string, opt ...Option) (*HostCatalog, error) {
const op = "plugin.NewHostCatalog"
opts := getOpts(opt...)
hc := &HostCatalog{
HostCatalog: &store.HostCatalog{
PluginId: pluginId,
ScopeId: scopeId,
Name: opts.withName,
Description: opts.withDescription,
},
}
if opts.withAttributes != nil {
attrs, err := json.Marshal(opts.withAttributes)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithCode(errors.InvalidParameter))
}
hc.Attributes = attrs
}
return hc, nil
}
func (c *HostCatalog) clone() *HostCatalog {
cp := proto.Clone(c.HostCatalog)
return &HostCatalog{
HostCatalog: cp.(*store.HostCatalog),
}
}
// 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
}
@@ -0,0 +1,92 @@
package plugin
import (
"context"
"encoding/json"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/host/plugin/store"
wrapping "github.com/hashicorp/go-kms-wrapping"
"github.com/hashicorp/go-kms-wrapping/structwrapping"
)
// 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, attributes map[string]interface{}, _ ...Option) (*HostCatalogSecret, error) {
const op = "plugin.newHostCatlogSecret"
hcs := &HostCatalogSecret{
HostCatalogSecret: &store.HostCatalogSecret{
CatalogId: catalogId,
},
}
if attributes != nil {
attrs, err := json.Marshal(attributes)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithCode(errors.InvalidParameter))
}
hcs.Secret = attrs
}
return hcs, nil
}
// 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 {
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
}
func (c *HostCatalogSecret) insertQuery() (query string, queryValues []interface{}) {
query = upsertHostCatalogSecretQuery
queryValues = []interface{}{
c.CatalogId,
c.CtSecret,
c.KeyId,
}
return
}
func (c *HostCatalogSecret) deleteQuery() (query string, queryValues []interface{}) {
query = deleteHostCatalogSecretQuery
queryValues = []interface{}{
c.CatalogId,
}
return
}
@@ -0,0 +1,163 @@
package plugin
import (
"context"
"encoding/json"
"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/testing/protocmp"
)
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", "test")
cat := TestCatalog(t, conn, plg.GetPublicId(), prj.GetPublicId())
type args struct {
catalogId string
attrs map[string]interface{}
}
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: map[string]interface{}{"foo": "bar"},
},
want: &HostCatalogSecret{
HostCatalogSecret: &store.HostCatalogSecret{
CatalogId: cat.GetPublicId(),
Secret: func() []byte {
b, err := json.Marshal(map[string]interface{}{"foo": "bar"})
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", "prefix")
cat := TestCatalog(t, conn, plg.GetPublicId(), prj.GetPublicId())
databaseWrapper, err := kkms.GetWrapper(ctx, prj.PublicId, kms.KeyPurposeDatabase)
hcs, err := newHostCatalogSecret(ctx, cat.GetPublicId(), map[string]interface{}{"foo": "bar"})
require.NoError(t, err)
assert.NoError(t, hcs.encrypt(ctx, databaseWrapper))
q, v := hcs.insertQuery()
_, err = rw.Exec(ctx, q, v)
assert.NoError(t, err)
found, err := newHostCatalogSecret(ctx, cat.GetPublicId(), nil)
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(), map[string]interface{}{"updated": "value"})
require.NoError(t, err)
assert.NoError(t, updated.encrypt(ctx, databaseWrapper))
q, v = updated.insertQuery()
_, 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))
}
+326
View File
@@ -0,0 +1,326 @@
package plugin
import (
"context"
"encoding/json"
"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/testing/protocmp"
)
func TestHostCatalog_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", "prefix")
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(map[string]interface{}{"foo": "bar"}),
},
},
want: func() *HostCatalog {
hc := &HostCatalog{
HostCatalog: &store.HostCatalog{
PluginId: plg.GetPublicId(),
ScopeId: prj.GetPublicId(),
},
}
var err error
hc.Attributes, err = json.Marshal(map[string]interface{}{"foo": "bar"})
require.NoError(t, err)
return hc
}(),
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
got, err := NewHostCatalog(context.Background(), tt.args.pluginId, tt.args.scopeId, 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()
assert.NoError(t, err)
tt.want.PublicId = id
got.PublicId = id
w := db.New(conn)
err = w.Create(context.Background(), 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(context.Background(), 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", "prefix1")
plg2 := host.TestPlugin(t, conn, "test2", "prefix2")
got, err := NewHostCatalog(context.Background(), plg.GetPublicId(), prj.GetPublicId(), WithName("duplicate"))
require.NoError(t, err)
got.PublicId, err = newHostCatalogId()
require.NoError(t, err)
w.Create(ctx, got)
// Can't create another resource with the same pluginName in the same scope
got.PublicId, err = newHostCatalogId()
require.NoError(t, err)
assert.Error(t, w.Create(ctx, got))
// Can't create another resource with same pluginName in same scope even for different plugin
got, err = NewHostCatalog(context.Background(), plg2.GetPublicId(), prj.GetPublicId(), WithName("duplicate"))
require.NoError(t, err)
got.PublicId, err = newHostCatalogId()
require.NoError(t, err)
assert.Error(t, w.Create(ctx, got))
// Can create another resource with same pluginName in different scope even for same plugin
got, err = NewHostCatalog(context.Background(), plg.GetPublicId(), prj2.GetPublicId(), WithName("duplicate"))
require.NoError(t, err)
got.PublicId, err = newHostCatalogId()
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", "prefix")
cat := TestCatalog(t, conn, plg.GetPublicId(), prj.GetPublicId())
ignoredCat := TestCatalog(t, conn, plg.GetPublicId(), prj.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", "deletescope")
cat := TestCatalog(t, conn, plg.GetPublicId(), prj.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", "deleteplugin")
cat := TestCatalog(t, conn, plg.GetPublicId(), prj.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())
})
}
}
+53
View File
@@ -0,0 +1,53 @@
package plugin
import (
"context"
"encoding/json"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/host/plugin/store"
)
// A HostSet is a collection of hosts from the set's catalog.
type HostSet struct {
*store.HostSet
tableName string `gorm:"-"`
}
// NewHostSet creates a new in memory HostSet assigned to catalogId.
// Attributes, name, and description 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...)
set := &HostSet{
HostSet: &store.HostSet{
CatalogId: catalogId,
Name: opts.withName,
Description: opts.withDescription,
},
}
if opts.withAttributes != nil {
attrs, err := json.Marshal(opts.withAttributes)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithCode(errors.InvalidParameter))
}
set.Attributes = attrs
}
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
}
+201
View File
@@ -0,0 +1,201 @@
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 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", "prefix")
cat := TestCatalog(t, conn, plg.GetPublicId(), prj.PublicId)
cat2 := TestCatalog(t, conn, plg.GetPublicId(), prj.PublicId)
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(map[string]interface{}{"foo": "bar"}),
},
},
want: &HostSet{
HostSet: &store.HostSet{
CatalogId: cat.GetPublicId(),
Attributes: []byte(`{"foo":"bar"}`),
},
},
},
}
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()
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())
})
}
}
+41
View File
@@ -0,0 +1,41 @@
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/types/subtypes"
)
func init() {
if err := host.Register(Subtype, HostCatalogPrefix, HostSetPrefix); err != nil {
panic(err)
}
}
// PublicId prefixes for the resources in the plugin package.
const (
// TODO: Pull these out of being constants and have them derivable at run time.
HostCatalogPrefix = "hcplg"
HostSetPrefix = "hsplg"
Subtype = subtypes.Subtype("plugin")
)
func newHostCatalogId() (string, error) {
// TODO: Take in a prefix portion as an argument.
id, err := db.NewPublicId(HostCatalogPrefix)
if err != nil {
return "", errors.WrapDeprecated(err, "plugin.newHostCatalogId")
}
return id, nil
}
func newHostSetId() (string, error) {
// TODO: Take in a prefix portion as an argument.
id, err := db.NewPublicId(HostSetPrefix)
if err != nil {
return "", errors.WrapDeprecated(err, "plugin.newHostSetId")
}
return id, nil
}
@@ -0,0 +1,163 @@
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/plugin/host"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/timestamppb"
)
func TestPluginCatalog_ImmutableFields(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
w := db.New(conn)
wrapper := db.TestWrapper(t)
ts := timestamp.Timestamp{Timestamp: &timestamppb.Timestamp{Seconds: 0, Nanos: 0}}
o, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
plg := host.TestPlugin(t, conn, "test", "prefix")
plg2 := host.TestPlugin(t, conn, "test2", "prefix2")
new := TestCatalog(t, conn, plg.GetPublicId(), prj.PublicId)
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)
ts := timestamp.Timestamp{Timestamp: &timestamppb.Timestamp{Seconds: 0, Nanos: 0}}
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
plg := host.TestPlugin(t, conn, "test", "prefix")
cat := TestCatalog(t, conn, plg.GetPublicId(), prj.PublicId)
new := TestSet(t, conn, cat.GetPublicId())
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: "plugin_host_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),
}
}
+59
View File
@@ -0,0 +1,59 @@
package plugin
// getOpts - iterate the inbound Options and return a struct
func getOpts(opt ...Option) options {
opts := getDefaultOptions()
for _, o := range opt {
o(&opts)
}
return opts
}
// Option - how Options are passed as arguments.
type Option func(*options)
// options = how options are represented
type options struct {
withName string
withDescription string
withLimit int
withAttributes map[string]interface{}
}
func getDefaultOptions() options {
return options{
withDescription: "",
withName: "",
withAttributes: make(map[string]interface{}),
}
}
// 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 map[string]interface{}) Option {
return func(o *options) {
o.withAttributes = attrs
}
}
// 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
}
}
+29
View File
@@ -0,0 +1,29 @@
package plugin
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_GetOpts(t *testing.T) {
t.Parallel()
t.Run("WithName", func(t *testing.T) {
opts := getOpts(WithName("test"))
testOpts := getDefaultOptions()
testOpts.withName = "test"
assert.Equal(t, opts, testOpts)
})
t.Run("WithDescription", func(t *testing.T) {
opts := getOpts(WithDescription("test desc"))
testOpts := getDefaultOptions()
testOpts.withDescription = "test desc"
assert.Equal(t, opts, testOpts)
})
t.Run("WithLimit", func(t *testing.T) {
opts := getOpts(WithLimit(5))
testOpts := getDefaultOptions()
testOpts.withLimit = 5
assert.Equal(t, opts, testOpts)
})
}
+19
View File
@@ -0,0 +1,19 @@
package plugin
const (
upsertHostCatalogSecretQuery = `
insert into host_plugin_catalog_secret
(catalog_id, secret, key_id)
values
($1, $2, $3)
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 = $1;
`
)
+566
View File
@@ -0,0 +1,566 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.15.8
// 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:"default:null"`
Attributes []byte `protobuf:"bytes,9,opt,name=attributes,proto3" json:"attributes,omitempty" gorm:"default: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"`
// name is optional. If set, it must be unique within
// catalog_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"`
// 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,6,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,7,opt,name=version,proto3" json:"version,omitempty" gorm:"default:null"`
// attributes is a jsonb formatted field.
// @inject_tag: `gorm:"default:null"`
Attributes []byte `protobuf:"bytes,8,opt,name=attributes,proto3" json:"attributes,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) 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
}
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 ""
}
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, 0x81, 0x03, 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, 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, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f,
0x69, 0x64, 0x18, 0x06, 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, 0x07,
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, 0x08, 0x20, 0x01, 0x28,
0x0c, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 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, 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, 3)
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
(*timestamp.Timestamp)(nil), // 3: controller.storage.timestamp.v1.Timestamp
}
var file_controller_storage_host_plugin_store_v1_host_proto_depIdxs = []int32{
3, // 0: controller.storage.host.plugin.store.v1.HostCatalog.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
3, // 1: controller.storage.host.plugin.store.v1.HostCatalog.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
3, // 2: controller.storage.host.plugin.store.v1.HostSet.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
3, // 3: controller.storage.host.plugin.store.v1.HostSet.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
3, // 4: controller.storage.host.plugin.store.v1.HostCatalogSecret.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
3, // 5: controller.storage.host.plugin.store.v1.HostCatalogSecret.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
6, // [6:6] is the sub-list for method output_type
6, // [6:6] is the sub-list for method input_type
6, // [6:6] is the sub-list for extension type_name
6, // [6:6] is the sub-list for extension extendee
0, // [0:6] 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
}
}
}
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: 3,
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
}
+50
View File
@@ -0,0 +1,50 @@
package plugin
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestCatalog creates count number of 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 *gorm.DB, pluginId, scopeId string, opt ...Option) *HostCatalog {
t.Helper()
ctx := context.Background()
cat, err := NewHostCatalog(ctx, pluginId, scopeId, opt...)
require.NoError(t, err)
assert.NotNil(t, cat)
id, err := newHostCatalogId()
assert.NoError(t, err)
assert.NotEmpty(t, id)
cat.PublicId = id
w := db.New(conn)
require.NoError(t, w.Create(ctx, cat))
return cat
}
// TestSet creates count number of 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 *gorm.DB, catalogId string, opt ...Option) *HostSet {
t.Helper()
ctx := context.Background()
assert := assert.New(t)
set, err := NewHostSet(ctx, catalogId, opt...)
require.NoError(t, err)
assert.NotNil(set)
id, err := newHostSetId()
assert.NoError(err)
assert.NotEmpty(id)
set.PublicId = id
w := db.New(conn)
require.NoError(t, w.Create(ctx, set))
return set
}
+48
View File
@@ -0,0 +1,48 @@
package plugin
import (
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/plugin/host"
"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", "prefix")
require.NotNil(plg)
assert.NotEmpty(plg.GetPublicId())
cs := TestCatalog(t, conn, plg.GetPublicId(), proj.GetPublicId(), WithName("foo"), WithDescription("bar"))
assert.NotEmpty(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)
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
require.NotNil(prj)
assert.NotEmpty(prj.GetPublicId())
plg := host.TestPlugin(t, conn, "test", "prefix")
require.NotNil(plg)
assert.NotEmpty(plg.GetPublicId())
c := TestCatalog(t, conn, plg.GetPublicId(), prj.GetPublicId())
set := TestSet(t, conn, c.GetPublicId(), WithName("foo"), WithDescription("bar"))
assert.NotEmpty(set.GetPublicId())
assert.Equal("foo", set.GetName())
assert.Equal("bar", set.GetDescription())
}
+3
View File
@@ -0,0 +1,3 @@
// Package plugin contains other packages related to the different plugin
// types and any helpers related to working with non type specific plugins.
package plugin
+4
View File
@@ -0,0 +1,4 @@
// Package host provides a plugin type used to interface with boundary's host
// related resources. Additionally it provides a repository for performing
// CRUDL and custom operations on this plugin type.
package host
+19
View File
@@ -0,0 +1,19 @@
package host
import (
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
)
// PublicId prefixes for the resources in the plugin package.
const (
PluginPrefix = "plgh"
)
func newPluginId() (string, error) {
id, err := db.NewPublicId(PluginPrefix)
if err != nil {
return "", errors.WrapDeprecated(err, "plugin.newPluginId")
}
return id, nil
}
@@ -0,0 +1,85 @@
package host
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/plugin/host/store"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/timestamppb"
)
func TestHostPlugin_ImmutableFields(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
w := db.New(conn)
ts := timestamp.Timestamp{Timestamp: &timestamppb.Timestamp{Seconds: 0, Nanos: 0}}
plg := TestPlugin(t, conn, "test", "prefix")
newPlugin := plg
tests := []struct {
name string
update *Plugin
fieldMask []string
}{
{
name: "public_id",
update: func() *Plugin {
c := newPlugin.testClonePlugin()
c.PublicId = "hc_thisIsNotAValidId"
return c
}(),
fieldMask: []string{"PublicId"},
},
{
name: "create time",
update: func() *Plugin {
c := newPlugin.testClonePlugin()
c.CreateTime = &ts
return c
}(),
fieldMask: []string{"CreateTime"},
},
{
name: "pluginName",
update: func() *Plugin {
c := newPlugin.testClonePlugin()
c.PluginName = "different pluginName"
return c
}(),
fieldMask: []string{"PluginName"},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
orig := newPlugin.testClonePlugin()
err := w.LookupById(context.Background(), orig)
require.NoError(err)
rowsUpdated, err := w.Update(context.Background(), tt.update, tt.fieldMask, nil, db.WithSkipVetForWrite(true))
require.Error(err)
assert.Equal(0, rowsUpdated)
after := newPlugin.testClonePlugin()
err = w.LookupById(context.Background(), after)
require.NoError(err)
assert.True(proto.Equal(orig, after))
})
}
}
func (c *Plugin) testClonePlugin() *Plugin {
cp := proto.Clone(c.Plugin)
return &Plugin{
Plugin: cp.(*store.Plugin),
}
}
+50
View File
@@ -0,0 +1,50 @@
package host
// getOpts - iterate the inbound Options and return a struct
func getOpts(opt ...Option) options {
opts := getDefaultOptions()
for _, o := range opt {
o(&opts)
}
return opts
}
// Option - how Options are passed as arguments.
type Option func(*options)
// options = how options are represented
type options struct {
withName string
withDescription string
withLimit int
}
func getDefaultOptions() options {
return options{
withDescription: "",
withName: "",
}
}
// WithDescription provides an optional description.
func WithDescription(desc string) Option {
return func(o *options) {
o.withDescription = desc
}
}
// WithName provides an optional name.
func WithName(name string) Option {
return func(o *options) {
o.withName = name
}
}
// 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
}
}
+29
View File
@@ -0,0 +1,29 @@
package host
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_GetOpts(t *testing.T) {
t.Parallel()
t.Run("WithName", func(t *testing.T) {
opts := getOpts(WithName("test"))
testOpts := getDefaultOptions()
testOpts.withName = "test"
assert.Equal(t, opts, testOpts)
})
t.Run("WithDescription", func(t *testing.T) {
opts := getOpts(WithDescription("test desc"))
testOpts := getDefaultOptions()
testOpts.withDescription = "test desc"
assert.Equal(t, opts, testOpts)
})
t.Run("WithLimit", func(t *testing.T) {
opts := getOpts(WithLimit(5))
testOpts := getDefaultOptions()
testOpts.withLimit = 5
assert.Equal(t, opts, testOpts)
})
}
+43
View File
@@ -0,0 +1,43 @@
package host
import (
"github.com/hashicorp/boundary/internal/plugin/host/store"
"github.com/hashicorp/boundary/internal/types/scope"
)
// A Plugin enables additional logic to be used by boundary.
// It is owned by a scope.
type Plugin struct {
*store.Plugin
tableName string `gorm:"-"`
}
// NewPlugin creates a new in memory Plugin assigned to the global scope.
// Name, Description are the only allowed option. All other options are ignored.
func NewPlugin(pluginName, idPrefix string, opt ...Option) *Plugin {
opts := getOpts(opt...)
p := &Plugin{
Plugin: &store.Plugin{
PluginName: pluginName,
IdPrefix: idPrefix,
ScopeId: scope.Global.String(),
Name: opts.withName,
Description: opts.withDescription,
},
}
return p
}
// TableName returns the table name for the host plugin.
func (c *Plugin) TableName() string {
if c.tableName != "" {
return c.tableName
}
return "plugin_host"
}
// SetTableName sets the table name. If the caller attempts to
// set the name to "" the name will be reset to the default name.
func (c *Plugin) SetTableName(n string) {
c.tableName = n
}
+275
View File
@@ -0,0 +1,275 @@
package host
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/types/scope"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/plugin/host/store"
)
func TestPlugin_Create(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
type args struct {
pluginName string
idPrefix string
semVer string
opts []Option
}
tests := []struct {
name string
args args
want *Plugin
wantErr bool
}{
{
name: "blank-pluginName",
args: args{
idPrefix: "prefix",
},
want: &Plugin{
Plugin: &store.Plugin{
IdPrefix: "prefix",
ScopeId: scope.Global.String(),
},
},
wantErr: true,
},
{
name: "blank-idPrefix",
args: args{
pluginName: "plugin name",
},
want: &Plugin{
Plugin: &store.Plugin{
PluginName: "plugin name",
ScopeId: scope.Global.String(),
},
},
wantErr: true,
},
{
name: "idprefix-capitalized",
args: args{
pluginName: "idprefixcapitalized",
idPrefix: "IdPrefixCapitalized",
},
want: &Plugin{
Plugin: &store.Plugin{
PluginName: "idprefixcapitalized",
IdPrefix: "IdPrefixCapitalized",
ScopeId: scope.Global.String(),
},
},
wantErr: true,
},
{
name: "idprefix-space",
args: args{
pluginName: "idprefix space",
idPrefix: "idprefix space",
},
want: &Plugin{
Plugin: &store.Plugin{
PluginName: "idprefix space",
IdPrefix: "idprefix space",
ScopeId: scope.Global.String(),
},
},
wantErr: true,
},
{
name: "pluginName-capitalized",
args: args{
pluginName: "PluginNameCapitalized",
idPrefix: "pluginnamecapitalized",
},
want: &Plugin{
Plugin: &store.Plugin{
PluginName: "PluginNameCapitalized",
IdPrefix: "pluginnamecapitalized",
ScopeId: scope.Global.String(),
},
},
wantErr: true,
},
{
name: "valid-no-options",
args: args{
pluginName: "validnooptions",
idPrefix: "validnooptions",
},
want: &Plugin{
Plugin: &store.Plugin{
PluginName: "validnooptions",
IdPrefix: "validnooptions",
ScopeId: scope.Global.String(),
},
},
},
{
name: "valid-with-description",
args: args{
pluginName: "validwithdescription",
idPrefix: "validwithdescription",
opts: []Option{
WithDescription("description"),
},
},
want: &Plugin{
Plugin: &store.Plugin{
PluginName: "validwithdescription",
IdPrefix: "validwithdescription",
ScopeId: scope.Global.String(),
Description: "description",
},
},
},
{
name: "valid-pluginName-name-option",
args: args{
pluginName: "validpluginnamenameoption",
idPrefix: "validpluginnamenameoption",
opts: []Option{
WithName("valid-pluginName-name-option"),
WithDescription("description"),
},
},
want: &Plugin{
Plugin: &store.Plugin{
PluginName: "validpluginnamenameoption",
IdPrefix: "validpluginnamenameoption",
ScopeId: scope.Global.String(),
Name: "valid-pluginName-name-option",
Description: "description",
},
},
},
// This must be run after the "valid-no-options" test
{
name: "duplicate-pluginName",
args: args{
pluginName: "validnooptions",
idPrefix: "duplicatepluginname",
},
want: &Plugin{
Plugin: &store.Plugin{
PluginName: "validnooptions",
IdPrefix: "duplicatepluginname",
ScopeId: scope.Global.String(),
},
},
wantErr: true,
},
{
name: "duplicate-idPrefix",
args: args{
pluginName: "duplicateidprefix",
idPrefix: "validnooptions",
},
want: &Plugin{
Plugin: &store.Plugin{
PluginName: "duplicateidprefix",
IdPrefix: "validnooptions",
ScopeId: scope.Global.String(),
},
},
wantErr: true,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
got := NewPlugin(tt.args.pluginName, tt.args.idPrefix, tt.args.opts...)
require.NotNil(t, got)
require.Emptyf(t, got.PublicId, "PublicId set")
id, err := newPluginId()
require.NoError(t, err)
got.PublicId = id
tt.want.PublicId = id
assert.Equal(t, tt.want, got)
w := db.New(conn)
err = w.Create(context.Background(), got)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestPlugin_Update(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
w := db.New(conn)
ctx := context.Background()
plg := TestPlugin(t, conn, "delete-plugin", "prefix")
assert.Equal(t, uint32(1), plg.Version)
plg.Name = "New"
plg.Description = "Description"
rowCount, err := w.Update(ctx, plg, []string{"Name", "Description"}, nil)
require.NoError(t, err)
assert.Equal(t, 1, rowCount)
assert.Equal(t, uint32(2), plg.Version)
assert.Equal(t, "New", plg.Name)
assert.Equal(t, "Description", plg.Description)
}
func TestPlugin_Delete(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
w := db.New(conn)
ctx := context.Background()
plg := TestPlugin(t, conn, "delete-plugin", "prefix")
deleted, err := w.Delete(ctx, plg)
require.NoError(t, err)
require.Equal(t, 1, deleted)
}
func TestPlugin_SetTableName(t *testing.T) {
defaultTableName := "plugin_host"
tests := []struct {
name string
initialName string
setNameTo string
want string
}{
{
name: "new-pluginName",
initialName: "",
setNameTo: "new-pluginName",
want: "new-pluginName",
},
{
name: "reset to default",
initialName: "initial",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := NewPlugin("", "prefix")
require.Equal(defaultTableName, def.TableName())
s := &Plugin{
Plugin: &store.Plugin{},
tableName: tt.initialName,
}
s.SetTableName(tt.setNameTo)
assert.Equal(tt.want, s.TableName())
})
}
}
+267
View File
@@ -0,0 +1,267 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.15.8
// source: controller/storage/plugin/host/store/v1/plugin.proto
// Package store provides protobufs for storing types in the host package.
package store
import (
timestamp "github.com/hashicorp/boundary/internal/db/timestamp"
_ "github.com/hashicorp/boundary/sdk/pbs/controller/protooptions"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Plugin struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// public_id is a surrogate key suitable for use in a public API.
// @inject_tag: `gorm:"primary_key"`
PublicId string `protobuf:"bytes,10,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"`
// The create_time is set by the database.
// @inject_tag: `gorm:"default:current_timestamp"`
CreateTime *timestamp.Timestamp `protobuf:"bytes,20,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"`
// The update_time is set by the database.
// @inject_tag: `gorm:"default:current_timestamp"`
UpdateTime *timestamp.Timestamp `protobuf:"bytes,30,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" gorm:"default:current_timestamp"`
// name is optional. If set, it must be unique within scope_id.
// @inject_tag: `gorm:"default:null"`
Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty" gorm:"default:null"`
// description is optional.
// @inject_tag: `gorm:"default:null"`
Description string `protobuf:"bytes,50,opt,name=description,proto3" json:"description,omitempty" gorm:"default:null"`
// The scope_id of the owning scope and must be set.
// @inject_tag: `gorm:"not_null"`
ScopeId string `protobuf:"bytes,60,opt,name=scope_id,json=scopeId,proto3" json:"scope_id,omitempty" gorm:"not_null"`
// @inject_tag: `gorm:"default:null"`
Version uint32 `protobuf:"varint,70,opt,name=version,proto3" json:"version,omitempty" gorm:"default:null"`
// plugin_name is required. The value must be unique across all host plugins
// @inject_tag: `gorm:"default:null"`
PluginName string `protobuf:"bytes,80,opt,name=plugin_name,json=pluginName,proto3" json:"plugin_name,omitempty" gorm:"default:null"`
// id_prefix is required. The value must be unique across all host plugins
// @inject_tag: `gorm:"default:null"`
IdPrefix string `protobuf:"bytes,90,opt,name=id_prefix,json=idPrefix,proto3" json:"id_prefix,omitempty" gorm:"default:null"`
}
func (x *Plugin) Reset() {
*x = Plugin{}
if protoimpl.UnsafeEnabled {
mi := &file_controller_storage_plugin_host_store_v1_plugin_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Plugin) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Plugin) ProtoMessage() {}
func (x *Plugin) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_plugin_host_store_v1_plugin_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Plugin.ProtoReflect.Descriptor instead.
func (*Plugin) Descriptor() ([]byte, []int) {
return file_controller_storage_plugin_host_store_v1_plugin_proto_rawDescGZIP(), []int{0}
}
func (x *Plugin) GetPublicId() string {
if x != nil {
return x.PublicId
}
return ""
}
func (x *Plugin) GetCreateTime() *timestamp.Timestamp {
if x != nil {
return x.CreateTime
}
return nil
}
func (x *Plugin) GetUpdateTime() *timestamp.Timestamp {
if x != nil {
return x.UpdateTime
}
return nil
}
func (x *Plugin) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *Plugin) GetDescription() string {
if x != nil {
return x.Description
}
return ""
}
func (x *Plugin) GetScopeId() string {
if x != nil {
return x.ScopeId
}
return ""
}
func (x *Plugin) GetVersion() uint32 {
if x != nil {
return x.Version
}
return 0
}
func (x *Plugin) GetPluginName() string {
if x != nil {
return x.PluginName
}
return ""
}
func (x *Plugin) GetIdPrefix() string {
if x != nil {
return x.IdPrefix
}
return ""
}
var File_controller_storage_plugin_host_store_v1_plugin_proto protoreflect.FileDescriptor
var file_controller_storage_plugin_host_store_v1_plugin_proto_rawDesc = []byte{
0x0a, 0x34, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f,
0x72, 0x61, 0x67, 0x65, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x68, 0x6f, 0x73, 0x74,
0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x27, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c,
0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69,
0x6e, 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x1a,
0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f, 0x72,
0x61, 0x67, 0x65, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2f, 0x76, 0x31,
0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x1a, 0x2a, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x63, 0x75, 0x73,
0x74, 0x6f, 0x6d, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x6f,
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9a, 0x03, 0x0a,
0x06, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69,
0x63, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c,
0x69, 0x63, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74,
0x69, 0x6d, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74,
0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74,
0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65,
0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d,
0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65,
0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65,
0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x24,
0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x10, 0xc2, 0xdd,
0x29, 0x0c, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x04,
0x6e, 0x61, 0x6d, 0x65, 0x12, 0x40, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
0x69, 0x6f, 0x6e, 0x18, 0x32, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1e, 0xc2, 0xdd, 0x29, 0x1a, 0x0a,
0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x64, 0x65,
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72,
0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f,
0x69, 0x64, 0x18, 0x3c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x49,
0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x46, 0x20, 0x01,
0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x70,
0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x50, 0x20, 0x01, 0x28, 0x09,
0x52, 0x0a, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09,
0x69, 0x64, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x5a, 0x20, 0x01, 0x28, 0x09, 0x52,
0x08, 0x69, 0x64, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x42, 0x40, 0x5a, 0x3e, 0x67, 0x69, 0x74,
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72,
0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72,
0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2f,
0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
}
var (
file_controller_storage_plugin_host_store_v1_plugin_proto_rawDescOnce sync.Once
file_controller_storage_plugin_host_store_v1_plugin_proto_rawDescData = file_controller_storage_plugin_host_store_v1_plugin_proto_rawDesc
)
func file_controller_storage_plugin_host_store_v1_plugin_proto_rawDescGZIP() []byte {
file_controller_storage_plugin_host_store_v1_plugin_proto_rawDescOnce.Do(func() {
file_controller_storage_plugin_host_store_v1_plugin_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_storage_plugin_host_store_v1_plugin_proto_rawDescData)
})
return file_controller_storage_plugin_host_store_v1_plugin_proto_rawDescData
}
var file_controller_storage_plugin_host_store_v1_plugin_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_controller_storage_plugin_host_store_v1_plugin_proto_goTypes = []interface{}{
(*Plugin)(nil), // 0: controller.storage.plugin.host.store.v1.Plugin
(*timestamp.Timestamp)(nil), // 1: controller.storage.timestamp.v1.Timestamp
}
var file_controller_storage_plugin_host_store_v1_plugin_proto_depIdxs = []int32{
1, // 0: controller.storage.plugin.host.store.v1.Plugin.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
1, // 1: controller.storage.plugin.host.store.v1.Plugin.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
2, // [2:2] is the sub-list for method output_type
2, // [2:2] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_controller_storage_plugin_host_store_v1_plugin_proto_init() }
func file_controller_storage_plugin_host_store_v1_plugin_proto_init() {
if File_controller_storage_plugin_host_store_v1_plugin_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_controller_storage_plugin_host_store_v1_plugin_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Plugin); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_controller_storage_plugin_host_store_v1_plugin_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_controller_storage_plugin_host_store_v1_plugin_proto_goTypes,
DependencyIndexes: file_controller_storage_plugin_host_store_v1_plugin_proto_depIdxs,
MessageInfos: file_controller_storage_plugin_host_store_v1_plugin_proto_msgTypes,
}.Build()
File_controller_storage_plugin_host_store_v1_plugin_proto = out.File
file_controller_storage_plugin_host_store_v1_plugin_proto_rawDesc = nil
file_controller_storage_plugin_host_store_v1_plugin_proto_goTypes = nil
file_controller_storage_plugin_host_store_v1_plugin_proto_depIdxs = nil
}
+22
View File
@@ -0,0 +1,22 @@
package host
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/require"
)
func TestPlugin(t *testing.T, conn *gorm.DB, name string, prefix string) *Plugin {
t.Helper()
p := NewPlugin(name, prefix)
id, err := newPluginId()
require.NoError(t, err)
p.PublicId = id
w := db.New(conn)
require.NoError(t, w.Create(context.Background(), p))
return p
}
+18
View File
@@ -0,0 +1,18 @@
package host
import (
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_TestPlugins(t *testing.T) {
assert, require := assert.New(t), require.New(t)
conn, _ := db.TestSetup(t, "postgres")
plg := TestPlugin(t, conn, "test", "prefix")
require.NotNil(plg)
assert.NotEmpty(plg.GetPublicId())
}
+72
View File
@@ -0,0 +1,72 @@
package plugin
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/plugin/store"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestPlugin_ImmutableFields(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
w := db.New(conn)
plg := testPlugin(t, conn, "test")
newPlugin := plg
tests := []struct {
name string
update *plugin
fieldMask []string
}{
{
name: "public_id",
update: func() *plugin {
c := newPlugin.testClonePlugin()
c.PublicId = "pi_thisIsNotAValidId"
return c
}(),
fieldMask: []string{"PublicId"},
},
{
name: "scope",
update: func() *plugin {
c := newPlugin.testClonePlugin()
c.ScopeId = "o_1234567890"
return c
}(),
fieldMask: []string{"ScopeId"},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
orig := newPlugin.testClonePlugin()
err := w.LookupById(context.Background(), orig)
require.NoError(err)
rowsUpdated, err := w.Update(context.Background(), tt.update, tt.fieldMask, nil, db.WithSkipVetForWrite(true))
require.Error(err)
assert.Equal(0, rowsUpdated)
after := newPlugin.testClonePlugin()
err = w.LookupById(context.Background(), after)
require.NoError(err)
assert.True(proto.Equal(orig, after))
})
}
}
func (c *plugin) testClonePlugin() *plugin {
cp := proto.Clone(c.Plugin)
return &plugin{
Plugin: cp.(*store.Plugin),
}
}
+182
View File
@@ -0,0 +1,182 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.15.8
// source: controller/storage/plugin/store/v1/plugin.proto
// Package store provides protobufs for storing types in the host package.
package store
import (
_ "github.com/hashicorp/boundary/internal/db/timestamp"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// This is a typeless plugin. In practice this should never be used directly.
// This is included for testing purposes only.
type Plugin struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// public_id is a surrogate key suitable for use in a public API.
// @inject_tag: `gorm:"primary_key"`
PublicId string `protobuf:"bytes,10,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"`
// The scope_id of the owning scope and must be set.
// @inject_tag: `gorm:"not_null"`
ScopeId string `protobuf:"bytes,20,opt,name=scope_id,json=scopeId,proto3" json:"scope_id,omitempty" gorm:"not_null"`
// name is optional. If set, it must be unique within scope_id.
// @inject_tag: `gorm:"default:null"`
Name string `protobuf:"bytes,30,opt,name=name,proto3" json:"name,omitempty" gorm:"default:null"`
}
func (x *Plugin) Reset() {
*x = Plugin{}
if protoimpl.UnsafeEnabled {
mi := &file_controller_storage_plugin_store_v1_plugin_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Plugin) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Plugin) ProtoMessage() {}
func (x *Plugin) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_plugin_store_v1_plugin_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Plugin.ProtoReflect.Descriptor instead.
func (*Plugin) Descriptor() ([]byte, []int) {
return file_controller_storage_plugin_store_v1_plugin_proto_rawDescGZIP(), []int{0}
}
func (x *Plugin) GetPublicId() string {
if x != nil {
return x.PublicId
}
return ""
}
func (x *Plugin) GetScopeId() string {
if x != nil {
return x.ScopeId
}
return ""
}
func (x *Plugin) GetName() string {
if x != nil {
return x.Name
}
return ""
}
var File_controller_storage_plugin_store_v1_plugin_proto protoreflect.FileDescriptor
var file_controller_storage_plugin_store_v1_plugin_proto_rawDesc = []byte{
0x0a, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f,
0x72, 0x61, 0x67, 0x65, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x73, 0x74, 0x6f, 0x72,
0x65, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x12, 0x22, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74,
0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x73, 0x74, 0x6f,
0x72, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
0x72, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
0x61, 0x6d, 0x70, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x54, 0x0a, 0x06, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e,
0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20,
0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x19, 0x0a,
0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52,
0x07, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x3b, 0x5a, 0x39,
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69,
0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e,
0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x73, 0x74,
0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x33,
}
var (
file_controller_storage_plugin_store_v1_plugin_proto_rawDescOnce sync.Once
file_controller_storage_plugin_store_v1_plugin_proto_rawDescData = file_controller_storage_plugin_store_v1_plugin_proto_rawDesc
)
func file_controller_storage_plugin_store_v1_plugin_proto_rawDescGZIP() []byte {
file_controller_storage_plugin_store_v1_plugin_proto_rawDescOnce.Do(func() {
file_controller_storage_plugin_store_v1_plugin_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_storage_plugin_store_v1_plugin_proto_rawDescData)
})
return file_controller_storage_plugin_store_v1_plugin_proto_rawDescData
}
var file_controller_storage_plugin_store_v1_plugin_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_controller_storage_plugin_store_v1_plugin_proto_goTypes = []interface{}{
(*Plugin)(nil), // 0: controller.storage.plugin.store.v1.Plugin
}
var file_controller_storage_plugin_store_v1_plugin_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_controller_storage_plugin_store_v1_plugin_proto_init() }
func file_controller_storage_plugin_store_v1_plugin_proto_init() {
if File_controller_storage_plugin_store_v1_plugin_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_controller_storage_plugin_store_v1_plugin_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Plugin); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_controller_storage_plugin_store_v1_plugin_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_controller_storage_plugin_store_v1_plugin_proto_goTypes,
DependencyIndexes: file_controller_storage_plugin_store_v1_plugin_proto_depIdxs,
MessageInfos: file_controller_storage_plugin_store_v1_plugin_proto_msgTypes,
}.Build()
File_controller_storage_plugin_store_v1_plugin_proto = out.File
file_controller_storage_plugin_store_v1_plugin_proto_rawDesc = nil
file_controller_storage_plugin_store_v1_plugin_proto_goTypes = nil
file_controller_storage_plugin_store_v1_plugin_proto_depIdxs = nil
}
+55
View File
@@ -0,0 +1,55 @@
package plugin
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/plugin/store"
"github.com/hashicorp/boundary/internal/types/scope"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/require"
)
// A typeless plugin used for tests.
type plugin struct {
*store.Plugin
tableName string `gorm:"-"`
}
// newPlugin is used in tests and creates a typeless plugin in the global scope.
func newPlugin(name string) *plugin {
p := &plugin{
Plugin: &store.Plugin{
ScopeId: scope.Global.String(),
Name: name,
},
}
return p
}
// TableName returns the table name for the host plugin.
func (c *plugin) TableName() string {
if c.tableName != "" {
return c.tableName
}
return "plugin"
}
// SetTableName sets the table name. If the caller attempts to
// set the name to "" the name will be reset to the default name.
func (c *plugin) SetTableName(n string) {
c.tableName = n
}
func testPlugin(t *testing.T, conn *gorm.DB, name string) *plugin {
t.Helper()
p := newPlugin(name)
id, err := db.NewPublicId("plg")
require.NoError(t, err)
p.PublicId = id
w := db.New(conn)
require.NoError(t, w.Create(context.Background(), p))
return p
}
+18
View File
@@ -0,0 +1,18 @@
package plugin
import (
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_testPlugin(t *testing.T) {
assert, require := assert.New(t), require.New(t)
conn, _ := db.TestSetup(t, "postgres")
plg := testPlugin(t, conn, "test")
require.NotNil(plg)
assert.NotEmpty(plg.GetPublicId())
}
@@ -0,0 +1,112 @@
syntax = "proto3";
// Package store provides protobufs for storing types in the plugin host
// package.
package controller.storage.host.plugin.store.v1;
option go_package = "github.com/hashicorp/boundary/internal/host/plugin/store;store";
import "google/protobuf/struct.proto";
import "controller/storage/timestamp/v1/timestamp.proto";
import "controller/custom_options/v1/options.proto";
message HostCatalog {
// public_id is a surrogate key suitable for use in a public API.
// @inject_tag: `gorm:"primary_key"`
string public_id = 1;
// The create_time is set by the database.
// @inject_tag: `gorm:"default:current_timestamp"`
timestamp.v1.Timestamp create_time = 2;
// The update_time is set by the database.
// @inject_tag: `gorm:"default:current_timestamp"`
timestamp.v1.Timestamp update_time = 3;
// name is optional. If set, it must be unique within scope_id.
// @inject_tag: `gorm:"default:null"`
string name = 4 [(custom_options.v1.mask_mapping) = {this:"name" that: "name"}];
// description is optional.
// @inject_tag: `gorm:"default:null"`
string description = 5 [(custom_options.v1.mask_mapping) = {this:"description" that: "description"}];
// The scope_id of the owning scope and must be set.
// @inject_tag: `gorm:"not_null"`
string scope_id = 6;
// The public id of the plugin this catalog uses.
// @inject_tag: `gorm:"not_null"`
string plugin_id = 7;
// version allows optimistic locking of the resource
// @inject_tag: `gorm:"default:null"`
uint32 version = 8;
// attributes is a jsonb formatted field.
// @inject_tag: `gorm:"default:null"`
bytes attributes = 9;
}
message HostSet {
// public_id is a surrogate key suitable for use in a public API.
// @inject_tag: `gorm:"primary_key"`
string public_id = 1;
// The create_time is set by the database.
// @inject_tag: `gorm:"default:current_timestamp"`
timestamp.v1.Timestamp create_time = 2;
// The update_time is set by the database.
// @inject_tag: `gorm:"default:current_timestamp"`
timestamp.v1.Timestamp update_time = 3;
// name is optional. If set, it must be unique within
// catalog_id.
// @inject_tag: `gorm:"default:null"`
string name = 4 [(custom_options.v1.mask_mapping) = {this:"name" that: "name"}];
// description is optional.
// @inject_tag: `gorm:"default:null"`
string description = 5 [(custom_options.v1.mask_mapping) = {this:"description" that: "description"}];
// catalog_id is the public_id of the owning
// plugin_host_catalog and must be set.
// @inject_tag: `gorm:"not_null"`
string catalog_id = 6;
// version allows optimistic locking of the resource
// @inject_tag: `gorm:"default:null"`
uint32 version = 7;
// attributes is a jsonb formatted field.
// @inject_tag: `gorm:"default:null"`
bytes attributes = 8;
}
message HostCatalogSecret {
// catalog_id is the public id of the catalog containing this secret.
// @inject_tag: `gorm:"primary_key"`
string catalog_id = 1;
// The create_time is set by the database.
// @inject_tag: `gorm:"default:current_timestamp"`
timestamp.v1.Timestamp create_time = 2;
// The update_time is set by the database.
// @inject_tag: `gorm:"default:current_timestamp"`
timestamp.v1.Timestamp update_time = 3;
// attributes is the plain-text of the attribute data. We are not storing
// this plain-text value in the database.
// @inject_tag: `gorm:"-" wrapping:"pt,secret_data"`
bytes secret = 4;
// ct_attributes is the ciphertext of the attribute data stored in the db.
// @inject_tag: `gorm:"column:secret;not_null" wrapping:"ct,secret_data"`
bytes ct_secret = 5;
// The key_id of the kms database key used for encrypting this entry.
// It must be set.
// @inject_tag: `gorm:"not_null"`
string key_id = 6;
}
@@ -0,0 +1,45 @@
syntax = "proto3";
// Package store provides protobufs for storing types in the host package.
package controller.storage.plugin.host.store.v1;
option go_package = "github.com/hashicorp/boundary/internal/plugin/host/store;store";
import "controller/storage/timestamp/v1/timestamp.proto";
import "controller/custom_options/v1/options.proto";
message Plugin {
// public_id is a surrogate key suitable for use in a public API.
// @inject_tag: `gorm:"primary_key"`
string public_id = 10;
// The create_time is set by the database.
// @inject_tag: `gorm:"default:current_timestamp"`
timestamp.v1.Timestamp create_time = 20;
// The update_time is set by the database.
// @inject_tag: `gorm:"default:current_timestamp"`
timestamp.v1.Timestamp update_time = 30;
// name is optional. If set, it must be unique within scope_id.
// @inject_tag: `gorm:"default:null"`
string name = 4 [(custom_options.v1.mask_mapping) = {this:"Name" that: "name"}];
// description is optional.
// @inject_tag: `gorm:"default:null"`
string description = 50 [(custom_options.v1.mask_mapping) = {this:"Description" that: "description"}];
// The scope_id of the owning scope and must be set.
// @inject_tag: `gorm:"not_null"`
string scope_id = 60;
// @inject_tag: `gorm:"default:null"`
uint32 version = 70;
// plugin_name is required. The value must be unique across all host plugins
// @inject_tag: `gorm:"default:null"`
string plugin_name = 80;
// id_prefix is required. The value must be unique across all host plugins
// @inject_tag: `gorm:"default:null"`
string id_prefix = 90;
}
@@ -0,0 +1,23 @@
syntax = "proto3";
// Package store provides protobufs for storing types in the host package.
package controller.storage.plugin.store.v1;
option go_package = "github.com/hashicorp/boundary/internal/plugin/store;store";
import "controller/storage/timestamp/v1/timestamp.proto";
// This is a typeless plugin. In practice this should never be used directly.
// This is included for testing purposes only.
message Plugin {
// public_id is a surrogate key suitable for use in a public API.
// @inject_tag: `gorm:"primary_key"`
string public_id = 10;
// The scope_id of the owning scope and must be set.
// @inject_tag: `gorm:"not_null"`
string scope_id = 20;
// name is optional. If set, it must be unique within scope_id.
// @inject_tag: `gorm:"default:null"`
string name = 30;
}
@@ -121,7 +121,7 @@ func TestPlugin(t *testing.T) {
// Create test plugin id
testPluginId := "pi_test1234"
numRows, err := rw.Exec(context.Background(), "insert into plugin(public_id) values (?)", []interface{}{testPluginId})
numRows, err := rw.Exec(context.Background(), "insert into plugin(public_id, scope_id) values (?, 'global')", []interface{}{testPluginId})
require.NoError(err)
assert.Equal(1, numRows)
@@ -154,7 +154,7 @@ func TestPlugin(t *testing.T) {
// Create test plugin id
testPluginId := "pi_test1234"
numRows, err := rw.Exec(context.Background(), "insert into plugin(public_id) values (?)", []interface{}{testPluginId})
numRows, err := rw.Exec(context.Background(), "insert into plugin(public_id, scope_id) values (?, 'global')", []interface{}{testPluginId})
assert.NoError(err)
assert.Equal(1, numRows)
@@ -177,7 +177,7 @@ func TestPlugin(t *testing.T) {
// Create test plugin id
testPluginId := "pi_test1234"
numRows, err := rw.Exec(context.Background(), "insert into plugin(public_id) values (?)", []interface{}{testPluginId})
numRows, err := rw.Exec(context.Background(), "insert into plugin(public_id, scope_id) values (?, 'global')", []interface{}{testPluginId})
assert.NoError(err)
assert.Equal(1, numRows)