Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
e8b7669
Define protos for simple namespaces CRUD
jakedoublev Jan 19, 2024
a36c14a
add generated namespaces sdk files
jakedoublev Jan 19, 2024
722a1ce
add grpcurl namespaces examples
jakedoublev Jan 19, 2024
a4b2db2
feat: implement attributes
jrschumacher Jan 19, 2024
3665174
add db layer for namespaces and add the serviceclient to the SDK afte…
jakedoublev Jan 19, 2024
d44cb49
merging in latest work
jakedoublev Jan 19, 2024
f8fbb90
provide namespace back in response when creating/updating and add ser…
jakedoublev Jan 20, 2024
3817b5a
make sure to register namespaces service on start
jakedoublev Jan 20, 2024
44505c0
namespaces cleanup
jakedoublev Jan 22, 2024
7f3d571
namespaces test suite boilerplate
jakedoublev Jan 22, 2024
17130db
Merge branch 'policy-config-changes' into feat/namespaces
jakedoublev Jan 22, 2024
493a999
Merge branch 'policy-config-changes' into feat/namespaces
jakedoublev Jan 23, 2024
2ffca7a
Merge branch 'policy-config-changes' into feat/namespaces
jakedoublev Jan 23, 2024
00bbaac
Merge branch 'policy-config-changes' into feat/namespaces
jakedoublev Jan 23, 2024
8d3a621
move all row scanning to db layer
jakedoublev Jan 23, 2024
2ac05e5
service work for namespaces
jakedoublev Jan 23, 2024
c0b7de3
use tableField func in attributes
jakedoublev Jan 23, 2024
49e3a7e
use proper namespace table name
jakedoublev Jan 23, 2024
3939449
require name and id, but id only once on update, and require only nam…
jakedoublev Jan 23, 2024
81cbd0b
ensure working crud of namespaces
jakedoublev Jan 23, 2024
9ee0cb2
lint fix
jakedoublev Jan 23, 2024
6ef08cb
fix grpcurl update example
jakedoublev Jan 23, 2024
4eacdee
add helper for checking constraint violations
jakedoublev Jan 23, 2024
624b006
improve error handling
jakedoublev Jan 23, 2024
6b541a3
consume error handling functions
jakedoublev Jan 23, 2024
c733264
Merge branch 'policy-config-changes' into feat/namespaces
jakedoublev Jan 24, 2024
ecea196
update to define and test more types of postgres 'bad request' type e…
jakedoublev Jan 24, 2024
03f27f9
consume latest error helper updates
jakedoublev Jan 24, 2024
0df765c
validate working error handling with logs and messages in namespaces
jakedoublev Jan 24, 2024
35b1b44
fix deletion
jakedoublev Jan 24, 2024
f4925e2
improve error wrapping by moving it down into the query and exec leve…
jakedoublev Jan 24, 2024
9b2efb4
consume latest db error changes
jakedoublev Jan 24, 2024
8f9804f
avoid nil pointer dereference
jakedoublev Jan 25, 2024
6a8d55d
Merge branch 'policy-config-changes' into feat/namespaces
jakedoublev Jan 25, 2024
86f55ff
declutter diff with varied lint settings
jakedoublev Jan 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions internal/db/attributes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package db

import (
"context"

sq "github.com/Masterminds/squirrel"
"github.com/jackc/pgx/v5"
"github.com/opentdf/opentdf-v2-poc/sdk/attributes"
)

func getAttributeByDefinitionSql(definition_id string) (string, []interface{}, error) {
return newStatementBuilder().
Select("*").
Join("attribute_definitions ON attribute_definitions.id = attribute_values.id").
Where(sq.Eq{"id": definition_id}).
From("attribute_values").
ToSql()
}
func (c Client) GetAttribute(ctx context.Context, definition_id string) (pgx.Row, error) {
sql, args, err := getAttributeByDefinitionSql(definition_id)
return c.queryRow(ctx, sql, args, err)
}

func getAttributesByNamespaceSql(namespaceId string) (string, []interface{}, error) {
return newStatementBuilder().
Select("*").
Join("attribute_definitions ON attribute_definitions.id = attribute_values.id").
Where(sq.Eq{"namespace_id": namespaceId}).
From("attribute_values").
ToSql()
}
func (c Client) GetAttributesByNamespace(ctx context.Context, namespaceId string) (pgx.Rows, error) {
sql, args, err := getAttributesByNamespaceSql(namespaceId)
return c.query(ctx, sql, args, err)
}

func createAttributeSql(namespaceId string, name string, rule string) (string, []interface{}, error) {
return newStatementBuilder().
Insert("attribute_values").
Columns("namespace_id", "name", "rule").
Values(namespaceId, name, rule).
ToSql()
}
func (c Client) CreateAttribute(ctx context.Context, def *attributes.Definition) error {
sql, args, err := createAttributeSql(def.NamespaceId, def.Name, removeProtobufEnumPrefix(def.Rule.String()))
return c.exec(ctx, sql, args, err)
}
37 changes: 36 additions & 1 deletion internal/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,9 +226,13 @@ func (c Client) UpdateResource(ctx context.Context, descriptor *common.ResourceD
return err
}

func newStatementBuilder() sq.StatementBuilderType {
return sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
}

func updateResourceSQL(descriptor *common.ResourceDescriptor,
resource []byte, policyType string) (string, []interface{}, error) {
psql := sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
psql := newStatementBuilder()

builder := psql.Update("opentdf.resources")

Expand Down Expand Up @@ -270,3 +274,34 @@ func deleteResourceSQL(id int32, policyType string) (string, []interface{}, erro
//nolint:wrapcheck // Wrapped error in DeleteResource
return builder.ToSql()
}

// Common function for all queryRow calls
func (c Client) queryRow(ctx context.Context, sql string, args []interface{}, err error) (pgx.Row, error) {
if err != nil {
slog.Debug("sql", slog.String("sql", sql), slog.Any("args", args))
return nil, fmt.Errorf("failed to create get resource sql: %w", err)
}
slog.Debug("sql", slog.String("sql", sql), slog.Any("args", args))
return c.QueryRow(ctx, sql, args...), nil
}

// Common function for all query calls
func (c Client) query(ctx context.Context, sql string, args []interface{}, err error) (pgx.Rows, error) {
if err != nil {
slog.Debug("sql", slog.String("sql", sql), slog.Any("args", args))
return nil, fmt.Errorf("failed to create list resource sql: %w", err)
}
slog.Debug("sql", slog.String("sql", sql), slog.Any("args", args))
return c.Query(ctx, sql, args...)
}

// Common function for all exec calls
func (c Client) exec(ctx context.Context, sql string, args []interface{}, err error) error {
if err != nil {
slog.Debug("sql", slog.String("sql", sql), slog.Any("args", args))
return fmt.Errorf("failed to create list resource sql: %w", err)
}
slog.Debug("sql", slog.String("sql", sql), slog.Any("args", args))
_, err = c.Exec(ctx, sql, args...)
return err
}
12 changes: 12 additions & 0 deletions internal/db/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package db

import "strings"

func removeProtobufEnumPrefix(s string) string {
// find the first instance of TYPE_
if strings.Contains(s, "TYPE_") {
// remove everything left of it
return s[strings.Index(s, "TYPE_")+5:]
}
return s
}
72 changes: 72 additions & 0 deletions internal/db/namespaces.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package db

import (
"context"

sq "github.com/Masterminds/squirrel"
"github.com/jackc/pgx/v5"
"github.com/opentdf/opentdf-v2-poc/sdk/namespaces"
)

func getNamespaceSql(id string) (string, []interface{}, error) {
return newStatementBuilder().
Select("*").
From("namespaces").
Where(sq.Eq{"id": id}).
ToSql()
}

func (c Client) GetNamespace(ctx context.Context, id string) (pgx.Row, error) {
sql, args, err := getNamespaceSql(id)
return c.queryRow(ctx, sql, args, err)
}

func listNamespacesSql() (string, []interface{}, error) {
return newStatementBuilder().
Select("*").
From("namespaces").
ToSql()
}

func (c Client) ListNamespaces(ctx context.Context) (pgx.Rows, error) {
sql, args, err := listNamespacesSql()
return c.query(ctx, sql, args, err)
}

func createNamespaceSql(namespace *namespaces.Namespace) (string, []interface{}, error) {
return newStatementBuilder().
Insert("namespaces").
Columns("name").
Values(namespace.Name).
ToSql()
}

func (c Client) CreateNamespace(ctx context.Context, namespace *namespaces.Namespace) error {
sql, args, err := createNamespaceSql(namespace)
return c.exec(ctx, sql, args, err)
}

func updateNamespaceSql(namespace *namespaces.Namespace) (string, []interface{}, error) {
return newStatementBuilder().
Update("namespaces").
Set("name", namespace.Name).
Where(sq.Eq{"id": namespace.Id}).
ToSql()
}

func (c Client) UpdateNamespace(ctx context.Context, namespace *namespaces.Namespace) error {
sql, args, err := updateNamespaceSql(namespace)
return c.exec(ctx, sql, args, err)
}

func deleteNamespaceSql(id string) (string, []interface{}, error) {
return newStatementBuilder().
Delete("namespaces").
Where(sq.Eq{"id": id}).
ToSql()
}

func (c Client) DeleteNamespace(ctx context.Context, id string) error {
sql, args, err := deleteNamespaceSql(id)
return c.exec(ctx, sql, args, err)
}
134 changes: 134 additions & 0 deletions proto/namespaces/namespaces.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
syntax = "proto3";

package namespaces;

import "buf/validate/validate.proto";
import "google/api/annotations.proto";

message Namespace {
// generated uuid in database
string id = 1;
// used to partition Attribute Definitions, support by namespace AuthN and enable federation
string name = 5 [
(buf.validate.field).required = true,
(buf.validate.field).string.max_len = 253,
(buf.validate.field).cel = {
id: "namespace_format",
message: "Namespace must be a valid hostname. It should include at least one dot, with each segment (label) starting and ending with an alphanumeric character. Each label must be 1 to 63 characters long, allowing hyphens but not as the first or last character. The top-level domain (the last segment after the final dot) must consist of at least two alphabetic characters.",
Comment thread
jrschumacher marked this conversation as resolved.
expression: "this.matches('^([a-zA-Z0-9]([a-zA-Z0-9\\\\-]{0,61}[a-zA-Z0-9])?\\\\.)+[a-zA-Z]{2,}$')"
}
];
}

/*

Namespace Service Definitions

*/

message GetNamespaceRequest {
string id = 1 [(buf.validate.field).required = true];
}
message GetNamespaceResponse {
Namespace namespace = 1;
}

message ListNamespacesRequest {}
message ListNamespacesResponse {
repeated Namespace namespaces = 1;
}

message CreateNamespaceRequest {
Namespace namespace = 1 [(buf.validate.field).required = true];
}
message CreateNamespaceResponse {
Namespace namespace = 1;
}

message UpdateNamespaceRequest {
string id = 1 [(buf.validate.field).required = true];
Namespace namespace = 2 [(buf.validate.field).required = true];
}
message UpdateNamespaceResponse {
Namespace namespace = 1;
}

message DeleteNamespaceRequest {
string id = 1 [(buf.validate.field).required = true];
}
message DeleteNamespaceResponse {}

service NamespaceService {
rpc GetNamespace(GetNamespaceRequest) returns (GetNamespaceResponse) {
option (google.api.http) = {
get: "/attributes/namespaces/{id}"
};
}
rpc ListNamespaces(ListNamespacesRequest) returns (ListNamespacesResponse) {
option (google.api.http) = {
get: "/attributes/namespaces"
};
}
rpc CreateNamespace(CreateNamespaceRequest) returns (CreateNamespaceResponse) {
option (google.api.http) = {
post: "/attributes/namespaces"
};
}
rpc UpdateNamespace(UpdateNamespaceRequest) returns (UpdateNamespaceResponse) {
option (google.api.http) = {
put: "/attributes/namespaces/{id}"
};
}
rpc DeleteNamespace(DeleteNamespaceRequest) returns (DeleteNamespaceResponse) {
option (google.api.http) = {
delete: "/attributes/namespaces/{id}"
};
}
}

/*

Namespace Service Examples

Create a Namespace:
Request:
grpcurl -plaintext -d @ localhost:9000 attributes.NamespaceService/CreateNamespace <<EOM
{
"namespace": {
"name": "example.com"
}
}
EOM
Response:
{
"namespace": {
"id": "b3d9e3e0-0b0a-4e1a-8b0a-0b0a0b0a0b0a",
"name": "example.com"
}
}

List Namespaces (assuming 3 have been created)
Request:
grpcurl -plaintext -d @ localhost:9000 attributes.NamespaceService/ListNamespaces <<EOM
{}
EOM
Response:
{
"namespaces": [
{
"id": "b3d9e3e0-0b0a-4e1a-8b0a-0b0a0b0a0b0a",
"name": "example.com"
},
{
"id": "b3d9e3e0-0b0a-4e1a-8b0a-0b0a0b0a0b0b",
"name": "loremipsum.com"
},
{
"id": "b3d9e3e0-0b0a-4e1a-8b0a-0b0a0b0a0b0c",
"name": "helloworld.com"
}
]
}


*/
Loading