From 2884e49c7b888ac977603b0e01b7277754798951 Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Thu, 12 Mar 2026 11:16:37 -0400 Subject: [PATCH 01/26] require namespace in otdfctl actions commands and re-enable actions/RR e2e paths --- cmd/policy/actions.go | 44 ++++++++++++++++++++++++++++--- docs/man/policy/actions/_index.md | 4 +-- docs/man/policy/actions/create.md | 12 +++++---- docs/man/policy/actions/delete.md | 6 ++++- docs/man/policy/actions/get.md | 6 ++++- docs/man/policy/actions/list.md | 6 ++++- docs/man/policy/actions/update.md | 8 ++++-- e2e/actions.bats | 19 +++++-------- e2e/registered-resources.bats | 10 +++---- go.mod | 2 +- go.sum | 4 +-- pkg/handlers/actions.go | 39 ++++++++++++++++++++------- 12 files changed, 113 insertions(+), 47 deletions(-) diff --git a/cmd/policy/actions.go b/cmd/policy/actions.go index 0d68e27a..7c0eae95 100644 --- a/cmd/policy/actions.go +++ b/cmd/policy/actions.go @@ -17,12 +17,13 @@ func policyGetAction(cmd *cobra.Command, args []string) { id := c.Flags.GetOptionalID("id") name := c.Flags.GetOptionalString("name") + namespace := c.Flags.GetRequiredString("namespace") if id == "" && name == "" { cli.ExitWithError("Either 'id' or 'name' must be provided", nil) } - action, err := h.GetAction(cmd.Context(), id, name) + action, err := h.GetAction(cmd.Context(), id, name, namespace) if err != nil { identifier := fmt.Sprintf("id: %s", id) if id == "" { @@ -51,8 +52,9 @@ func policyListActions(cmd *cobra.Command, args []string) { limit := c.Flags.GetRequiredInt32("limit") offset := c.Flags.GetRequiredInt32("offset") + namespace := c.Flags.GetRequiredString("namespace") - resp, err := h.ListActions(cmd.Context(), limit, offset) + resp, err := h.ListActions(cmd.Context(), limit, offset, namespace) if err != nil { cli.ExitWithError("Failed to list actions", err) } @@ -88,9 +90,10 @@ func policyCreateAction(cmd *cobra.Command, args []string) { defer h.Close() name := c.Flags.GetRequiredString("name") + namespace := c.Flags.GetRequiredString("namespace") metadataLabels = c.Flags.GetStringSlice("label", metadataLabels, cli.FlagsStringSliceOptions{Min: 0}) - action, err := h.CreateAction(cmd.Context(), name, getMetadataMutable(metadataLabels)) + action, err := h.CreateAction(cmd.Context(), name, namespace, getMetadataMutable(metadataLabels)) if err != nil { cli.ExitWithError("Failed to create action", err) } @@ -114,10 +117,11 @@ func policyDeleteAction(cmd *cobra.Command, args []string) { defer h.Close() id := c.Flags.GetRequiredID("id") + namespace := c.Flags.GetRequiredString("namespace") force := c.Flags.GetOptionalBool("force") ctx := cmd.Context() - action, err := h.GetAction(ctx, id, "") + action, err := h.GetAction(ctx, id, "", namespace) if err != nil { errMsg := fmt.Sprintf("Failed to find action (%s)", id) cli.ExitWithError(errMsg, err) @@ -144,6 +148,7 @@ func policyUpdateAction(cmd *cobra.Command, args []string) { defer h.Close() id := c.Flags.GetRequiredID("id") + namespace := c.Flags.GetRequiredString("namespace") name := c.Flags.GetOptionalString("name") metadataLabels = c.Flags.GetStringSlice("label", metadataLabels, cli.FlagsStringSliceOptions{Min: 0}) @@ -151,6 +156,7 @@ func policyUpdateAction(cmd *cobra.Command, args []string) { cmd.Context(), id, name, + namespace, getMetadataMutable(metadataLabels), getMetadataUpdateBehavior(), ) @@ -182,10 +188,22 @@ func initActionsCommands() { getDoc.GetDocFlag("name").Default, getDoc.GetDocFlag("name").Description, ) + getDoc.Flags().StringP( + getDoc.GetDocFlag("namespace").Name, + getDoc.GetDocFlag("namespace").Shorthand, + getDoc.GetDocFlag("namespace").Default, + getDoc.GetDocFlag("namespace").Description, + ) listDoc := man.Docs.GetCommand("policy/actions/list", man.WithRun(policyListActions), ) + listDoc.Flags().StringP( + listDoc.GetDocFlag("namespace").Name, + listDoc.GetDocFlag("namespace").Shorthand, + listDoc.GetDocFlag("namespace").Default, + listDoc.GetDocFlag("namespace").Description, + ) injectListPaginationFlags(listDoc) createDoc := man.Docs.GetCommand("policy/actions/create", @@ -197,6 +215,12 @@ func initActionsCommands() { createDoc.GetDocFlag("name").Default, createDoc.GetDocFlag("name").Description, ) + createDoc.Flags().StringP( + createDoc.GetDocFlag("namespace").Name, + createDoc.GetDocFlag("namespace").Shorthand, + createDoc.GetDocFlag("namespace").Default, + createDoc.GetDocFlag("namespace").Description, + ) injectLabelFlags(&createDoc.Command, false) updateDoc := man.Docs.GetCommand("policy/actions/update", @@ -214,6 +238,12 @@ func initActionsCommands() { updateDoc.GetDocFlag("name").Default, updateDoc.GetDocFlag("name").Description, ) + updateDoc.Flags().StringP( + updateDoc.GetDocFlag("namespace").Name, + updateDoc.GetDocFlag("namespace").Shorthand, + updateDoc.GetDocFlag("namespace").Default, + updateDoc.GetDocFlag("namespace").Description, + ) injectLabelFlags(&updateDoc.Command, true) deleteDoc := man.Docs.GetCommand("policy/actions/delete", @@ -225,6 +255,12 @@ func initActionsCommands() { deleteDoc.GetDocFlag("id").Default, deleteDoc.GetDocFlag("id").Description, ) + deleteDoc.Flags().StringP( + deleteDoc.GetDocFlag("namespace").Name, + deleteDoc.GetDocFlag("namespace").Shorthand, + deleteDoc.GetDocFlag("namespace").Default, + deleteDoc.GetDocFlag("namespace").Description, + ) deleteDoc.Flags().Bool( deleteDoc.GetDocFlag("force").Name, false, diff --git a/docs/man/policy/actions/_index.md b/docs/man/policy/actions/_index.md index 438c5c54..9c2f1725 100644 --- a/docs/man/policy/actions/_index.md +++ b/docs/man/policy/actions/_index.md @@ -16,11 +16,11 @@ Standard Actions in Policy are comprised of the below, and only their metadata l - update - delete -Custom Actions known to Policy are admin-defined, globally unique (not namespaced), and will be lower +Custom Actions known to Policy are admin-defined, unique within a namespace, and will be lower cased when stored. They may contain underscores (`_`) or hyphens (`-`) if preceded or followed by an alphanumeric character. For example: - download - queue-to-print - send_email -For more information about entitlement and Subject Mappings, see the `subject-mappings` command. \ No newline at end of file +For more information about entitlement and Subject Mappings, see the `subject-mappings` command. diff --git a/docs/man/policy/actions/create.md b/docs/man/policy/actions/create.md index c7025d34..312dff93 100644 --- a/docs/man/policy/actions/create.md +++ b/docs/man/policy/actions/create.md @@ -9,7 +9,11 @@ command: flags: - name: name shorthand: n - description: Name of the custom action (must be unique within Policy) + description: Name of the custom action (must be unique within a namespace) + required: true + - name: namespace + shorthand: s + description: Namespace ID or FQN required: true - name: label description: "Optional metadata 'labels' in the format: key=value" @@ -20,8 +24,7 @@ command: Add a custom `action` to the platform Policy. An Action `name` is normalized to lower case and may contain underscores (`_`) or hyphens (`-`) -between other alphanumeric characters. Each name must be globally unique as actions are not -namespaced. +between other alphanumeric characters. Each name must be unique within a namespace. For more information, see the `actions` subcommand. @@ -30,6 +33,5 @@ For more information, see the `actions` subcommand. Create a custom action named 'install_package': ```shell -otdfctl policy actions create --name install_package +otdfctl policy actions create --name install_package --namespace https://example.com ``` - diff --git a/docs/man/policy/actions/delete.md b/docs/man/policy/actions/delete.md index 0a67c706..3a2c9ee9 100644 --- a/docs/man/policy/actions/delete.md +++ b/docs/man/policy/actions/delete.md @@ -7,6 +7,10 @@ command: shorthand: i description: ID of the custom action required: true + - name: namespace + shorthand: s + description: Namespace ID or FQN + required: true - name: force description: Force deletion without interactive confirmation --- @@ -24,5 +28,5 @@ For more information about Actions, see the manual for the `actions` subcommand. ## Example ```shell -otdfctl policy actions delete --id 217b300a-47f9-4bee-be8c-d38c880053f7 +otdfctl policy actions delete --id 217b300a-47f9-4bee-be8c-d38c880053f7 --namespace https://example.com ``` diff --git a/docs/man/policy/actions/get.md b/docs/man/policy/actions/get.md index 0c451224..6ed92060 100644 --- a/docs/man/policy/actions/get.md +++ b/docs/man/policy/actions/get.md @@ -11,6 +11,10 @@ command: - name: name shorthand: n description: Name of the action + - name: namespace + shorthand: s + description: Namespace ID or FQN + required: true --- If both `id` and `name` flag values are provided, `id` is preferred. @@ -28,5 +32,5 @@ otdfctl policy actions get --id e1402c63-eeaa-45e2-85d2-b939d135941f Get by Name: ```shell -otdfctl policy actions get --name read +otdfctl policy actions get --name read --namespace https://example.com ``` diff --git a/docs/man/policy/actions/list.md b/docs/man/policy/actions/list.md index fc41472d..49874775 100644 --- a/docs/man/policy/actions/list.md +++ b/docs/man/policy/actions/list.md @@ -5,6 +5,10 @@ command: aliases: - l flags: + - name: namespace + shorthand: s + description: Namespace ID or FQN + required: true - name: limit shorthand: l description: Limit retrieved count @@ -18,5 +22,5 @@ For more information about Actions, see the manual for the `actions` subcommand. ## Example ```shell -otdfctl policy actions list +otdfctl policy actions list --namespace https://example.com ``` diff --git a/docs/man/policy/actions/update.md b/docs/man/policy/actions/update.md index bcae2748..5e21dcb8 100644 --- a/docs/man/policy/actions/update.md +++ b/docs/man/policy/actions/update.md @@ -9,9 +9,13 @@ command: shorthand: i description: ID of the action to update required: true + - name: namespace + shorthand: s + description: Namespace ID or FQN + required: true - name: name shorthand: n - description: Optional updated name of the custom action (must be unique within Policy) + description: Optional updated name of the custom action (must be unique within a namespace) - name: label description: "Optional metadata 'labels' in the format: key=value" shorthand: l @@ -32,5 +36,5 @@ For more information about Actions, see the manual for the `actions` subcommand. ## Example ```shell -otdfctl policy actions update --id 34c62145-5d99-45cb-a732-13cb16270e63 --name new_action_name +otdfctl policy actions update --id 34c62145-5d99-45cb-a732-13cb16270e63 --name new_action_name --namespace https://example.com ``` diff --git a/e2e/actions.bats b/e2e/actions.bats index aa7d20f7..da2977fa 100644 --- a/e2e/actions.bats +++ b/e2e/actions.bats @@ -5,6 +5,7 @@ setup_file() { export WITH_CREDS='--with-client-creds-file ./creds.json' export HOST='--host http://localhost:8080' + export ACTION_NAMESPACE='https://example.com' } setup() { @@ -13,17 +14,16 @@ setup() { # invoke binary with credentials run_otdfctl_action () { - run sh -c "./otdfctl $HOST $WITH_CREDS policy actions $*" + run sh -c "./otdfctl $HOST $WITH_CREDS policy actions $* --namespace $ACTION_NAMESPACE" } } teardown_file() { # clear out all test env vars - unset HOST WITH_CREDS + unset HOST WITH_CREDS ACTION_NAMESPACE } @test "Create a new custom action - Good" { - skip "Temporarily disabled [namespaced-actions]: actions now require namespace flags" run_otdfctl_action create --name test_action_create assert_output --partial "SUCCESS" assert_line --regexp "Name.*test_action_create" @@ -37,7 +37,6 @@ teardown_file() { } @test "Create a new action - Bad" { - skip "Temporarily disabled [namespaced-actions]: actions now require namespace flags" # bad action names run_otdfctl_action create --name ends_underscored_ assert_failure @@ -58,14 +57,13 @@ teardown_file() { } @test "Get an action - Good" { - skip "Temporarily disabled [namespaced-actions]: actions now require namespace flags" run_otdfctl_action get --name "read" assert_success assert_line --partial "Id" assert_line --regexp "Name.*read" # get by name to retrieve the ID - UPDATE_ACTION_ID=$(./otdfctl policy actions get --name update --json $HOST $WITH_CREDS | jq -r '.id') + UPDATE_ACTION_ID=$(./otdfctl policy actions get --name update --namespace "$ACTION_NAMESPACE" --json $HOST $WITH_CREDS | jq -r '.id') run_otdfctl_action get --id "$UPDATE_ACTION_ID" --json assert_success @@ -84,7 +82,6 @@ teardown_file() { } @test "List actions" { - skip "Temporarily disabled [namespaced-actions]: actions now require namespace flags" run_otdfctl_action list assert_output --partial "create" assert_output --partial "read" @@ -105,8 +102,7 @@ teardown_file() { } @test "Update action" { - skip "Temporarily disabled [namespaced-actions]: actions now require namespace flags" - ACTION_TO_UPDATE=$(./otdfctl policy actions create --name testing_updation $HOST $WITH_CREDS --json | jq -r '.id') + ACTION_TO_UPDATE=$(./otdfctl policy actions create --name testing_updation --namespace "$ACTION_NAMESPACE" $HOST $WITH_CREDS --json | jq -r '.id') # extend labels run_otdfctl_action update --id "$ACTION_TO_UPDATE" -l key=value --label test=true assert_success @@ -137,14 +133,13 @@ teardown_file() { } @test "Delete action - bad" { - STANDARD_ACTION=$(./otdfctl policy actions get --name update $HOST $WITH_CREDS --json | jq -r '.id') + STANDARD_ACTION=$(./otdfctl policy actions get --name update --namespace "$ACTION_NAMESPACE" $HOST $WITH_CREDS --json | jq -r '.id') run_otdfctl_action delete --id "$STANDARD_ACTION" --force assert_failure } @test "Delete action - good" { - skip "Temporarily disabled [namespaced-actions]: actions now require namespace flags" - DELETABLE_ACTION=$(./otdfctl policy actions create --name testing-delete $HOST $WITH_CREDS --json | jq -r '.id') + DELETABLE_ACTION=$(./otdfctl policy actions create --name testing-delete --namespace "$ACTION_NAMESPACE" $HOST $WITH_CREDS --json | jq -r '.id') run_otdfctl_action delete --id "$DELETABLE_ACTION" --force assert_success } diff --git a/e2e/registered-resources.bats b/e2e/registered-resources.bats index 9b06ad79..0a839eba 100644 --- a/e2e/registered-resources.bats +++ b/e2e/registered-resources.bats @@ -16,11 +16,11 @@ setup_file() { # create custom action to be used in registered resource values tests export CUSTOM_ACTION_NAME="test_action_for_values" - export CUSTOM_ACTION_ID=$(./otdfctl $HOST $WITH_CREDS policy actions create --name "$CUSTOM_ACTION_NAME" --json | jq -r '.id') + export CUSTOM_ACTION_ID=$(./otdfctl $HOST $WITH_CREDS policy actions create --name "$CUSTOM_ACTION_NAME" --namespace "$NS_ID" --json | jq -r '.id') # get standard read action id to use in registered resource values tests export READ_ACTION_NAME="read" - export READ_ACTION_ID=$(./otdfctl $HOST $WITH_CREDS policy actions get --name "$READ_ACTION_NAME" --json | jq -r '.id') + export READ_ACTION_ID=$(./otdfctl $HOST $WITH_CREDS policy actions get --name "$READ_ACTION_NAME" --namespace "$NS_ID" --json | jq -r '.id') export ATTR_NAME=test_rr_attr attr_id=$(./otdfctl $HOST $WITH_CREDS policy attributes create --namespace "$NS_ID" --name "$ATTR_NAME" --rule ANY_OF -l key=value --json | jq -r '.id') export ATTR_VAL_1_ID=$(./otdfctl $HOST $WITH_CREDS policy attributes values create --attribute-id "$attr_id" --value test_reg_res_attr__val_1 --json | jq -r '.id') @@ -49,7 +49,7 @@ teardown_file() { ./otdfctl $HOST $WITH_CREDS policy registered-resources delete --id "$RR_ID" --force # remove the custom action used in registered resource values tests - ./otdfctl $HOST $WITH_CREDS policy actions delete --id "$CUSTOM_ACTION_ID" --force + ./otdfctl $HOST $WITH_CREDS policy actions delete --id "$CUSTOM_ACTION_ID" --namespace "$NS_ID" --force # remove the namespace and cascade delete attributes and values used in registered resource values tests ./otdfctl $HOST $WITH_CREDS policy attributes namespaces unsafe delete --id "$NS_ID" --force @@ -212,7 +212,6 @@ teardown_file() { # Tests for registered resource values @test "Create a registered resource value - Good" { - skip "Temporarily disabled [namespaced-actions]: action-name validation/path is failing in CI" # simple by resource ID run_otdfctl_reg_res_values create --resource "$RR_ID" --value test_create_rr_val assert_output --partial "SUCCESS" @@ -286,7 +285,6 @@ teardown_file() { } @test "Get a registered resource value - Good" { - skip "Temporarily disabled [namespaced-actions]: action-name validation/path is failing in CI" # setup a resource value to get run_otdfctl_reg_res_values create --resource "$RR_ID" --value test_get_rr_val --action-attribute-value "\"$READ_ACTION_ID;$ATTR_VAL_1_ID\"" assert_success @@ -333,7 +331,6 @@ teardown_file() { } @test "List registered resource values - Good" { - skip "Temporarily disabled [namespaced-actions]: dependent registered resource value setup is failing in CI" # setup values to list run_otdfctl_reg_res_values create --resource "$RR_ID" --value test_list_rr_val_1 --action-attribute-value "\"$READ_ACTION_ID;$ATTR_VAL_1_ID\"" reg_res_val1_id=$(echo "$output" | grep Id | awk -F'│' '{print $3}' | xargs) @@ -385,7 +382,6 @@ teardown_file() { } @test "Update registered resource values" { - skip "Temporarily disabled [namespaced-actions]: action-name validation/path is failing in CI" # setup a resource value to update run_otdfctl_reg_res_values create --resource "$RR_ID" --value test_update_rr_val --action-attribute-value "\"$READ_ACTION_ID;$ATTR_VAL_1_ID\"" assert_success diff --git a/go.mod b/go.mod index 7af4fe4b..f0dd7b59 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/jrschumacher/go-osprofiles v0.0.0-20251201220924-3d077c5481e5 github.com/opentdf/platform/lib/flattening v0.1.3 github.com/opentdf/platform/lib/ocrypto v0.10.0 - github.com/opentdf/platform/protocol/go v0.17.0 + github.com/opentdf/platform/protocol/go v0.17.1-0.20260312144647-bedc9b353661 github.com/opentdf/platform/sdk v0.13.0 github.com/spf13/cobra v1.10.2 github.com/stretchr/testify v1.11.1 diff --git a/go.sum b/go.sum index 60e43d84..9ce86472 100644 --- a/go.sum +++ b/go.sum @@ -242,8 +242,8 @@ github.com/opentdf/platform/lib/flattening v0.1.3 h1:IuOm/wJVXNrzOV676Ticgr0wyBk github.com/opentdf/platform/lib/flattening v0.1.3/go.mod h1:Gs/T+6FGZKk9OAdz2Jf1R8CTGeNRYrq1lZGDeYT3hrY= github.com/opentdf/platform/lib/ocrypto v0.10.0 h1:7dn/z/1qH3p+gWCrfOoU7hj9XF/p5N+b2JBJuWF9aK0= github.com/opentdf/platform/lib/ocrypto v0.10.0/go.mod h1:WASkoHreqgTFImB/gJW42VTdpi9AkgkmaW19y/fU+Ew= -github.com/opentdf/platform/protocol/go v0.17.0 h1:pZUnX/jp741SW/gYwa5eAj8P7yqHQV58cwPpxEDR/Ik= -github.com/opentdf/platform/protocol/go v0.17.0/go.mod h1:4lsBu86yrOWdhqIko8/x5ndamOtM8iDNZYBguF9ZiQQ= +github.com/opentdf/platform/protocol/go v0.17.1-0.20260312144647-bedc9b353661 h1:ThjukhY4AdzI7wrV3I1HBBI4WSCpzWfo3WPMstYXqRs= +github.com/opentdf/platform/protocol/go v0.17.1-0.20260312144647-bedc9b353661/go.mod h1:jeDa0o2jce1yELNxZ92hscz0mxxqq7GtqC9EoYCpNW0= github.com/opentdf/platform/sdk v0.13.0 h1:jhhCLE1Y57Y20g6TUEg/zqCfid8m4KPfbaaBlg6oAsM= github.com/opentdf/platform/sdk v0.13.0/go.mod h1:x80F65+dGzxDTq8iqVIbCrbDDB9oYCYGjh4duoH6biM= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= diff --git a/pkg/handlers/actions.go b/pkg/handlers/actions.go index 701b8ded..a78ca2dc 100644 --- a/pkg/handlers/actions.go +++ b/pkg/handlers/actions.go @@ -3,12 +3,13 @@ package handlers import ( "context" + "github.com/google/uuid" "github.com/opentdf/platform/protocol/go/common" "github.com/opentdf/platform/protocol/go/policy" "github.com/opentdf/platform/protocol/go/policy/actions" ) -func (h Handler) GetAction(ctx context.Context, id string, name string) (*policy.Action, error) { +func (h Handler) GetAction(ctx context.Context, id string, name string, namespace string) (*policy.Action, error) { req := &actions.GetActionRequest{} if id != "" { req.Identifier = &actions.GetActionRequest_Id{ @@ -20,6 +21,12 @@ func (h Handler) GetAction(ctx context.Context, id string, name string) (*policy } } + if _, err := uuid.Parse(namespace); err != nil { + req.NamespaceFqn = namespace + } else { + req.NamespaceId = namespace + } + resp, err := h.sdk.Actions.GetAction(ctx, req) if err != nil { return nil, err @@ -28,20 +35,34 @@ func (h Handler) GetAction(ctx context.Context, id string, name string) (*policy return resp.GetAction(), nil } -func (h Handler) ListActions(ctx context.Context, limit, offset int32) (*actions.ListActionsResponse, error) { - return h.sdk.Actions.ListActions(ctx, &actions.ListActionsRequest{ +func (h Handler) ListActions(ctx context.Context, limit, offset int32, namespace string) (*actions.ListActionsResponse, error) { + req := &actions.ListActionsRequest{ Pagination: &policy.PageRequest{ Limit: limit, Offset: offset, }, - }) + } + if _, err := uuid.Parse(namespace); err != nil { + req.NamespaceFqn = namespace + } else { + req.NamespaceId = namespace + } + + return h.sdk.Actions.ListActions(ctx, req) } -func (h Handler) CreateAction(ctx context.Context, name string, metadata *common.MetadataMutable) (*policy.Action, error) { - resp, err := h.sdk.Actions.CreateAction(ctx, &actions.CreateActionRequest{ +func (h Handler) CreateAction(ctx context.Context, name string, namespace string, metadata *common.MetadataMutable) (*policy.Action, error) { + req := &actions.CreateActionRequest{ Name: name, Metadata: metadata, - }) + } + if _, err := uuid.Parse(namespace); err != nil { + req.NamespaceFqn = namespace + } else { + req.NamespaceId = namespace + } + + resp, err := h.sdk.Actions.CreateAction(ctx, req) if err != nil { return nil, err } @@ -49,7 +70,7 @@ func (h Handler) CreateAction(ctx context.Context, name string, metadata *common return resp.GetAction(), nil } -func (h Handler) UpdateAction(ctx context.Context, id, name string, metadata *common.MetadataMutable, behavior common.MetadataUpdateEnum) (*policy.Action, error) { +func (h Handler) UpdateAction(ctx context.Context, id, name, namespace string, metadata *common.MetadataMutable, behavior common.MetadataUpdateEnum) (*policy.Action, error) { _, err := h.sdk.Actions.UpdateAction(ctx, &actions.UpdateActionRequest{ Id: id, Metadata: metadata, @@ -59,7 +80,7 @@ func (h Handler) UpdateAction(ctx context.Context, id, name string, metadata *co if err != nil { return nil, err } - return h.GetAction(ctx, id, "") + return h.GetAction(ctx, id, "", namespace) } func (h Handler) DeleteAction(ctx context.Context, id string) error { From 5e96d4d42e43a94cb6fa31f60196410ce4ca50f2 Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Thu, 12 Mar 2026 12:41:29 -0400 Subject: [PATCH 02/26] updates based on comments --- cmd/policy/actions.go | 52 ++++++++++--------------------- docs/man/policy/actions/delete.md | 6 +--- docs/man/policy/actions/get.md | 3 +- docs/man/policy/actions/update.md | 6 +--- pkg/handlers/actions.go | 6 ++-- 5 files changed, 24 insertions(+), 49 deletions(-) diff --git a/cmd/policy/actions.go b/cmd/policy/actions.go index 7c0eae95..9f29fcfc 100644 --- a/cmd/policy/actions.go +++ b/cmd/policy/actions.go @@ -17,11 +17,14 @@ func policyGetAction(cmd *cobra.Command, args []string) { id := c.Flags.GetOptionalID("id") name := c.Flags.GetOptionalString("name") - namespace := c.Flags.GetRequiredString("namespace") + namespace := c.Flags.GetOptionalString("namespace") if id == "" && name == "" { cli.ExitWithError("Either 'id' or 'name' must be provided", nil) } + if id == "" && name != "" && namespace == "" { + cli.ExitWithError("'namespace' must be provided when using 'name'", nil) + } action, err := h.GetAction(cmd.Context(), id, name, namespace) if err != nil { @@ -117,11 +120,10 @@ func policyDeleteAction(cmd *cobra.Command, args []string) { defer h.Close() id := c.Flags.GetRequiredID("id") - namespace := c.Flags.GetRequiredString("namespace") force := c.Flags.GetOptionalBool("force") ctx := cmd.Context() - action, err := h.GetAction(ctx, id, "", namespace) + action, err := h.GetAction(ctx, id, "", "") if err != nil { errMsg := fmt.Sprintf("Failed to find action (%s)", id) cli.ExitWithError(errMsg, err) @@ -148,7 +150,6 @@ func policyUpdateAction(cmd *cobra.Command, args []string) { defer h.Close() id := c.Flags.GetRequiredID("id") - namespace := c.Flags.GetRequiredString("namespace") name := c.Flags.GetOptionalString("name") metadataLabels = c.Flags.GetStringSlice("label", metadataLabels, cli.FlagsStringSliceOptions{Min: 0}) @@ -156,7 +157,6 @@ func policyUpdateAction(cmd *cobra.Command, args []string) { cmd.Context(), id, name, - namespace, getMetadataMutable(metadataLabels), getMetadataUpdateBehavior(), ) @@ -172,6 +172,15 @@ func policyUpdateAction(cmd *cobra.Command, args []string) { common.HandleSuccess(cmd, id, t, updated) } +func injectNamespaceFlag(doc *man.Doc) { + doc.Flags().StringP( + doc.GetDocFlag("namespace").Name, + doc.GetDocFlag("namespace").Shorthand, + doc.GetDocFlag("namespace").Default, + doc.GetDocFlag("namespace").Description, + ) +} + func initActionsCommands() { getDoc := man.Docs.GetCommand("policy/actions/get", man.WithRun(policyGetAction), @@ -188,22 +197,12 @@ func initActionsCommands() { getDoc.GetDocFlag("name").Default, getDoc.GetDocFlag("name").Description, ) - getDoc.Flags().StringP( - getDoc.GetDocFlag("namespace").Name, - getDoc.GetDocFlag("namespace").Shorthand, - getDoc.GetDocFlag("namespace").Default, - getDoc.GetDocFlag("namespace").Description, - ) + injectNamespaceFlag(getDoc) listDoc := man.Docs.GetCommand("policy/actions/list", man.WithRun(policyListActions), ) - listDoc.Flags().StringP( - listDoc.GetDocFlag("namespace").Name, - listDoc.GetDocFlag("namespace").Shorthand, - listDoc.GetDocFlag("namespace").Default, - listDoc.GetDocFlag("namespace").Description, - ) + injectNamespaceFlag(listDoc) injectListPaginationFlags(listDoc) createDoc := man.Docs.GetCommand("policy/actions/create", @@ -215,12 +214,7 @@ func initActionsCommands() { createDoc.GetDocFlag("name").Default, createDoc.GetDocFlag("name").Description, ) - createDoc.Flags().StringP( - createDoc.GetDocFlag("namespace").Name, - createDoc.GetDocFlag("namespace").Shorthand, - createDoc.GetDocFlag("namespace").Default, - createDoc.GetDocFlag("namespace").Description, - ) + injectNamespaceFlag(createDoc) injectLabelFlags(&createDoc.Command, false) updateDoc := man.Docs.GetCommand("policy/actions/update", @@ -238,12 +232,6 @@ func initActionsCommands() { updateDoc.GetDocFlag("name").Default, updateDoc.GetDocFlag("name").Description, ) - updateDoc.Flags().StringP( - updateDoc.GetDocFlag("namespace").Name, - updateDoc.GetDocFlag("namespace").Shorthand, - updateDoc.GetDocFlag("namespace").Default, - updateDoc.GetDocFlag("namespace").Description, - ) injectLabelFlags(&updateDoc.Command, true) deleteDoc := man.Docs.GetCommand("policy/actions/delete", @@ -255,12 +243,6 @@ func initActionsCommands() { deleteDoc.GetDocFlag("id").Default, deleteDoc.GetDocFlag("id").Description, ) - deleteDoc.Flags().StringP( - deleteDoc.GetDocFlag("namespace").Name, - deleteDoc.GetDocFlag("namespace").Shorthand, - deleteDoc.GetDocFlag("namespace").Default, - deleteDoc.GetDocFlag("namespace").Description, - ) deleteDoc.Flags().Bool( deleteDoc.GetDocFlag("force").Name, false, diff --git a/docs/man/policy/actions/delete.md b/docs/man/policy/actions/delete.md index 3a2c9ee9..0a67c706 100644 --- a/docs/man/policy/actions/delete.md +++ b/docs/man/policy/actions/delete.md @@ -7,10 +7,6 @@ command: shorthand: i description: ID of the custom action required: true - - name: namespace - shorthand: s - description: Namespace ID or FQN - required: true - name: force description: Force deletion without interactive confirmation --- @@ -28,5 +24,5 @@ For more information about Actions, see the manual for the `actions` subcommand. ## Example ```shell -otdfctl policy actions delete --id 217b300a-47f9-4bee-be8c-d38c880053f7 --namespace https://example.com +otdfctl policy actions delete --id 217b300a-47f9-4bee-be8c-d38c880053f7 ``` diff --git a/docs/man/policy/actions/get.md b/docs/man/policy/actions/get.md index 6ed92060..4ea0a107 100644 --- a/docs/man/policy/actions/get.md +++ b/docs/man/policy/actions/get.md @@ -14,11 +14,12 @@ command: - name: namespace shorthand: s description: Namespace ID or FQN - required: true --- If both `id` and `name` flag values are provided, `id` is preferred. +When using `--name`, `--namespace` is required. + For more information about Actions, see the manual for the `actions` subcommand. ## Example diff --git a/docs/man/policy/actions/update.md b/docs/man/policy/actions/update.md index 5e21dcb8..5deb0856 100644 --- a/docs/man/policy/actions/update.md +++ b/docs/man/policy/actions/update.md @@ -9,10 +9,6 @@ command: shorthand: i description: ID of the action to update required: true - - name: namespace - shorthand: s - description: Namespace ID or FQN - required: true - name: name shorthand: n description: Optional updated name of the custom action (must be unique within a namespace) @@ -36,5 +32,5 @@ For more information about Actions, see the manual for the `actions` subcommand. ## Example ```shell -otdfctl policy actions update --id 34c62145-5d99-45cb-a732-13cb16270e63 --name new_action_name --namespace https://example.com +otdfctl policy actions update --id 34c62145-5d99-45cb-a732-13cb16270e63 --name new_action_name ``` diff --git a/pkg/handlers/actions.go b/pkg/handlers/actions.go index a78ca2dc..8f85e5af 100644 --- a/pkg/handlers/actions.go +++ b/pkg/handlers/actions.go @@ -70,8 +70,8 @@ func (h Handler) CreateAction(ctx context.Context, name string, namespace string return resp.GetAction(), nil } -func (h Handler) UpdateAction(ctx context.Context, id, name, namespace string, metadata *common.MetadataMutable, behavior common.MetadataUpdateEnum) (*policy.Action, error) { - _, err := h.sdk.Actions.UpdateAction(ctx, &actions.UpdateActionRequest{ +func (h Handler) UpdateAction(ctx context.Context, id, name string, metadata *common.MetadataMutable, behavior common.MetadataUpdateEnum) (*policy.Action, error) { + resp, err := h.sdk.Actions.UpdateAction(ctx, &actions.UpdateActionRequest{ Id: id, Metadata: metadata, Name: name, @@ -80,7 +80,7 @@ func (h Handler) UpdateAction(ctx context.Context, id, name, namespace string, m if err != nil { return nil, err } - return h.GetAction(ctx, id, "", namespace) + return resp.GetAction(), nil } func (h Handler) DeleteAction(ctx context.Context, id string) error { From 3cb5c690aa5e8788aa8c9a175c3df2f2ef04ab5f Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Thu, 12 Mar 2026 12:43:14 -0400 Subject: [PATCH 03/26] add helper --- pkg/handlers/actions.go | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/pkg/handlers/actions.go b/pkg/handlers/actions.go index 8f85e5af..e6606dcb 100644 --- a/pkg/handlers/actions.go +++ b/pkg/handlers/actions.go @@ -9,6 +9,13 @@ import ( "github.com/opentdf/platform/protocol/go/policy/actions" ) +func getNamespaceIDAndFQN(namespace string) (id string, fqn string) { + if _, err := uuid.Parse(namespace); err != nil { + return "", namespace + } + return namespace, "" +} + func (h Handler) GetAction(ctx context.Context, id string, name string, namespace string) (*policy.Action, error) { req := &actions.GetActionRequest{} if id != "" { @@ -21,11 +28,7 @@ func (h Handler) GetAction(ctx context.Context, id string, name string, namespac } } - if _, err := uuid.Parse(namespace); err != nil { - req.NamespaceFqn = namespace - } else { - req.NamespaceId = namespace - } + req.NamespaceId, req.NamespaceFqn = getNamespaceIDAndFQN(namespace) resp, err := h.sdk.Actions.GetAction(ctx, req) if err != nil { @@ -42,11 +45,7 @@ func (h Handler) ListActions(ctx context.Context, limit, offset int32, namespace Offset: offset, }, } - if _, err := uuid.Parse(namespace); err != nil { - req.NamespaceFqn = namespace - } else { - req.NamespaceId = namespace - } + req.NamespaceId, req.NamespaceFqn = getNamespaceIDAndFQN(namespace) return h.sdk.Actions.ListActions(ctx, req) } @@ -56,11 +55,7 @@ func (h Handler) CreateAction(ctx context.Context, name string, namespace string Name: name, Metadata: metadata, } - if _, err := uuid.Parse(namespace); err != nil { - req.NamespaceFqn = namespace - } else { - req.NamespaceId = namespace - } + req.NamespaceId, req.NamespaceFqn = getNamespaceIDAndFQN(namespace) resp, err := h.sdk.Actions.CreateAction(ctx, req) if err != nil { From 46ba054efeb2ad3f17620bc6deb68583a13ca243 Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Thu, 12 Mar 2026 13:38:16 -0400 Subject: [PATCH 04/26] linting --- pkg/handlers/actions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/handlers/actions.go b/pkg/handlers/actions.go index e6606dcb..91978b64 100644 --- a/pkg/handlers/actions.go +++ b/pkg/handlers/actions.go @@ -9,7 +9,7 @@ import ( "github.com/opentdf/platform/protocol/go/policy/actions" ) -func getNamespaceIDAndFQN(namespace string) (id string, fqn string) { +func getNamespaceIDAndFQN(namespace string) (string, string) { if _, err := uuid.Parse(namespace); err != nil { return "", namespace } From 59cfa4997143237cb85f834f5f6facb814b7155a Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Thu, 12 Mar 2026 16:23:15 -0400 Subject: [PATCH 05/26] update protocol version, add bats --- e2e/actions.bats | 5 +++++ go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/e2e/actions.bats b/e2e/actions.bats index 2477016d..efabeb1a 100644 --- a/e2e/actions.bats +++ b/e2e/actions.bats @@ -68,6 +68,7 @@ teardown_file() { # get by name to retrieve the ID UPDATE_ACTION_ID=$(./otdfctl policy actions get --name update --namespace "$ACTION_NAMESPACE" --json $HOST $WITH_CREDS | jq -r '.id') + # ensure getting by id does not require namespace run_otdfctl_action get --id "$UPDATE_ACTION_ID" --json assert_success [ "$(echo "$output" | jq -r '.id')" = "$UPDATE_ACTION_ID" ] @@ -82,6 +83,10 @@ teardown_file() { run_otdfctl_action get --id 'testing_get' assert_failure assert_output --partial "must be a valid UUID" + + run_otdfctl_action get --name 'test_action_create' + assert_failure + assert_output --partial "namespace' must be provided when using 'name'" } @test "List actions" { diff --git a/go.mod b/go.mod index f0dd7b59..5203ca7a 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/jrschumacher/go-osprofiles v0.0.0-20251201220924-3d077c5481e5 github.com/opentdf/platform/lib/flattening v0.1.3 github.com/opentdf/platform/lib/ocrypto v0.10.0 - github.com/opentdf/platform/protocol/go v0.17.1-0.20260312144647-bedc9b353661 + github.com/opentdf/platform/protocol/go v0.19.0 github.com/opentdf/platform/sdk v0.13.0 github.com/spf13/cobra v1.10.2 github.com/stretchr/testify v1.11.1 diff --git a/go.sum b/go.sum index 9ce86472..d8b3d1d1 100644 --- a/go.sum +++ b/go.sum @@ -242,8 +242,8 @@ github.com/opentdf/platform/lib/flattening v0.1.3 h1:IuOm/wJVXNrzOV676Ticgr0wyBk github.com/opentdf/platform/lib/flattening v0.1.3/go.mod h1:Gs/T+6FGZKk9OAdz2Jf1R8CTGeNRYrq1lZGDeYT3hrY= github.com/opentdf/platform/lib/ocrypto v0.10.0 h1:7dn/z/1qH3p+gWCrfOoU7hj9XF/p5N+b2JBJuWF9aK0= github.com/opentdf/platform/lib/ocrypto v0.10.0/go.mod h1:WASkoHreqgTFImB/gJW42VTdpi9AkgkmaW19y/fU+Ew= -github.com/opentdf/platform/protocol/go v0.17.1-0.20260312144647-bedc9b353661 h1:ThjukhY4AdzI7wrV3I1HBBI4WSCpzWfo3WPMstYXqRs= -github.com/opentdf/platform/protocol/go v0.17.1-0.20260312144647-bedc9b353661/go.mod h1:jeDa0o2jce1yELNxZ92hscz0mxxqq7GtqC9EoYCpNW0= +github.com/opentdf/platform/protocol/go v0.19.0 h1:R3+1/doB5S53w4d3ApN2JuapWCXCSK3CYhXXrU6eZ9g= +github.com/opentdf/platform/protocol/go v0.19.0/go.mod h1:jeDa0o2jce1yELNxZ92hscz0mxxqq7GtqC9EoYCpNW0= github.com/opentdf/platform/sdk v0.13.0 h1:jhhCLE1Y57Y20g6TUEg/zqCfid8m4KPfbaaBlg6oAsM= github.com/opentdf/platform/sdk v0.13.0/go.mod h1:x80F65+dGzxDTq8iqVIbCrbDDB9oYCYGjh4duoH6biM= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= From ed6ca5fefcd0d644e0a1fbf3732ddcc9f62731ea Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Thu, 12 Mar 2026 16:25:20 -0400 Subject: [PATCH 06/26] unskip bats --- e2e/actions.bats | 3 --- e2e/registered-resources.bats | 3 --- 2 files changed, 6 deletions(-) diff --git a/e2e/actions.bats b/e2e/actions.bats index efabeb1a..5acdf2a6 100644 --- a/e2e/actions.bats +++ b/e2e/actions.bats @@ -3,9 +3,6 @@ # Tests for actions setup_file() { - # TODO: Remove this file-level skip once otdfctl passes namespace flags for the namespaced action APIs. - skip "Temporarily disabled [namespaced-actions]: platform actions APIs now require namespace flags" - export WITH_CREDS='--with-client-creds-file ./creds.json' export HOST='--host http://localhost:8080' export ACTION_NAMESPACE='https://example.com' diff --git a/e2e/registered-resources.bats b/e2e/registered-resources.bats index 9d4e11f7..0a839eba 100644 --- a/e2e/registered-resources.bats +++ b/e2e/registered-resources.bats @@ -3,9 +3,6 @@ # Tests for registered resources setup_file() { - # TODO: Remove this file-level skip once otdfctl passes namespace flags for the namespaced action APIs used by registered resource values. - skip "Temporarily disabled [namespaced-actions]: registered resource BATS setup still depends on pre-namespace action APIs" - export WITH_CREDS='--with-client-creds-file ./creds.json' export HOST='--host http://localhost:8080' From 0b8f199bc9b5b06f5b35e3f0679fce4d90aa1fb9 Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Thu, 12 Mar 2026 16:40:52 -0400 Subject: [PATCH 07/26] fix bats --- e2e/actions.bats | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/e2e/actions.bats b/e2e/actions.bats index 5acdf2a6..cd47bfd3 100644 --- a/e2e/actions.bats +++ b/e2e/actions.bats @@ -14,7 +14,7 @@ setup() { # invoke binary with credentials run_otdfctl_action () { - run sh -c "./otdfctl $HOST $WITH_CREDS policy actions $* --namespace $ACTION_NAMESPACE" + run sh -c "./otdfctl $HOST $WITH_CREDS policy actions $*" } } @@ -24,7 +24,7 @@ teardown_file() { } @test "Create a new custom action - Good" { - run_otdfctl_action create --name test_action_create + run_otdfctl_action create --name test_action_create --namespace "$ACTION_NAMESPACE" assert_output --partial "SUCCESS" assert_line --regexp "Name.*test_action_create" assert_output --partial "Id" @@ -38,26 +38,30 @@ teardown_file() { @test "Create a new action - Bad" { # bad action names - run_otdfctl_action create --name ends_underscored_ + run_otdfctl_action create --name ends_underscored_ --namespace "$ACTION_NAMESPACE" assert_failure - run_otdfctl_action create --name -first-char-hyphen + run_otdfctl_action create --name -first-char-hyphen --namespace "$ACTION_NAMESPACE" assert_failure - run_otdfctl_action create --name inval!d.chars + run_otdfctl_action create --name inval!d.chars --namespace "$ACTION_NAMESPACE" assert_failure # missing flag - run_otdfctl_action create + run_otdfctl_action create --namespace "$ACTION_NAMESPACE" assert_failure assert_output --partial "Flag '--name' is required" + + run_otdfctl_action create --name no_namespace + assert_failure + assert_output --partial "Flag '--namespace' is required" # conflict - run_otdfctl_action create -n "read" + run_otdfctl_action create -n "read" --namespace "$ACTION_NAMESPACE" assert_failure assert_output --partial "already_exists" } @test "Get an action - Good" { - run_otdfctl_action get --name "read" + run_otdfctl_action get --name "read" --namespace "$ACTION_NAMESPACE" assert_success assert_line --partial "Id" assert_line --regexp "Name.*read" @@ -81,13 +85,13 @@ teardown_file() { assert_failure assert_output --partial "must be a valid UUID" - run_otdfctl_action get --name 'test_action_create' + run_otdfctl_action get --name 'testing_get' assert_failure assert_output --partial "namespace' must be provided when using 'name'" } @test "List actions" { - run_otdfctl_action list + run_otdfctl_action list --namespace "$ACTION_NAMESPACE" assert_output --partial "create" assert_output --partial "read" assert_output --partial "update" @@ -95,7 +99,7 @@ teardown_file() { assert_output --partial "Total" assert_line --regexp "Current Offset.*0" - run_otdfctl_action list --json + run_otdfctl_action list --namespace "$ACTION_NAMESPACE" --json assert_success assert_not_equal $(echo "$output" | jq -r 'pagination') "null" assert_output --partial "create" From 408ca4a361313048589bf3da144295cdcdf99bf2 Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Thu, 12 Mar 2026 17:14:45 -0400 Subject: [PATCH 08/26] undo change to update action --- pkg/handlers/actions.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/handlers/actions.go b/pkg/handlers/actions.go index 91978b64..1e95927e 100644 --- a/pkg/handlers/actions.go +++ b/pkg/handlers/actions.go @@ -66,7 +66,7 @@ func (h Handler) CreateAction(ctx context.Context, name string, namespace string } func (h Handler) UpdateAction(ctx context.Context, id, name string, metadata *common.MetadataMutable, behavior common.MetadataUpdateEnum) (*policy.Action, error) { - resp, err := h.sdk.Actions.UpdateAction(ctx, &actions.UpdateActionRequest{ + _, err := h.sdk.Actions.UpdateAction(ctx, &actions.UpdateActionRequest{ Id: id, Metadata: metadata, Name: name, @@ -75,7 +75,7 @@ func (h Handler) UpdateAction(ctx context.Context, id, name string, metadata *co if err != nil { return nil, err } - return resp.GetAction(), nil + return h.GetAction(ctx, id, "", "") } func (h Handler) DeleteAction(ctx context.Context, id string) error { From 609dbeb13f324eb509bb4d068c29c58171c0b85f Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Thu, 12 Mar 2026 17:35:12 -0400 Subject: [PATCH 09/26] test against branch --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 28266de0..f6ee077f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -59,7 +59,7 @@ jobs: - name: Start up the platform with deps and containers uses: opentdf/platform/test/start-up-with-containers@main with: - platform-ref: "main" + platform-ref: "dspx-2450-namespaced-actions-db-updates" - uses: opentdf/otdfctl/e2e@main with: otdfctl-ref: ${{ github.event.pull_request.head.sha }} From 55db843c1d192e4d9729e1ece9c4553b71f2bd0d Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Thu, 12 Mar 2026 17:57:53 -0400 Subject: [PATCH 10/26] update bats with standard action create error --- e2e/actions.bats | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/e2e/actions.bats b/e2e/actions.bats index cd47bfd3..70497d10 100644 --- a/e2e/actions.bats +++ b/e2e/actions.bats @@ -56,8 +56,20 @@ teardown_file() { # conflict run_otdfctl_action create -n "read" --namespace "$ACTION_NAMESPACE" + assert_failure + assert_output --partial "intended action would violate a restriction" + + # duplicate custom action + run_otdfctl_action create --name test_action_conflict --namespace "$ACTION_NAMESPACE" + assert_success + conflict_action_id=$(echo "$output" | grep Id | awk -F'│' '{print $3}' | xargs) + + run_otdfctl_action create --name test_action_conflict --namespace "$ACTION_NAMESPACE" assert_failure assert_output --partial "already_exists" + + # cleanup + run_otdfctl_action delete --id "$conflict_action_id" --force } @test "Get an action - Good" { From 203c1e8e514a2097b002d7a1625924290e198a5a Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Fri, 13 Mar 2026 09:51:05 -0400 Subject: [PATCH 11/26] move back to main --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f6ee077f..28266de0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -59,7 +59,7 @@ jobs: - name: Start up the platform with deps and containers uses: opentdf/platform/test/start-up-with-containers@main with: - platform-ref: "dspx-2450-namespaced-actions-db-updates" + platform-ref: "main" - uses: opentdf/otdfctl/e2e@main with: otdfctl-ref: ${{ github.event.pull_request.head.sha }} From ecc6f292b70c231c5a5a8c7bfb8e9269ec67d096 Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Fri, 13 Mar 2026 10:28:13 -0400 Subject: [PATCH 12/26] dont require namespace, test against branch --- .github/workflows/ci.yaml | 2 +- cmd/policy/actions.go | 2 +- docs/man/policy/actions/list.md | 1 - e2e/actions.bats | 9 +++++++++ 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 28266de0..e1616a49 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -59,7 +59,7 @@ jobs: - name: Start up the platform with deps and containers uses: opentdf/platform/test/start-up-with-containers@main with: - platform-ref: "main" + platform-ref: "dspx-2450-list-actions-shouldnt-require-namespace" - uses: opentdf/otdfctl/e2e@main with: otdfctl-ref: ${{ github.event.pull_request.head.sha }} diff --git a/cmd/policy/actions.go b/cmd/policy/actions.go index 9f29fcfc..dd16eb6e 100644 --- a/cmd/policy/actions.go +++ b/cmd/policy/actions.go @@ -55,7 +55,7 @@ func policyListActions(cmd *cobra.Command, args []string) { limit := c.Flags.GetRequiredInt32("limit") offset := c.Flags.GetRequiredInt32("offset") - namespace := c.Flags.GetRequiredString("namespace") + namespace := c.Flags.GetOptionalString("namespace") resp, err := h.ListActions(cmd.Context(), limit, offset, namespace) if err != nil { diff --git a/docs/man/policy/actions/list.md b/docs/man/policy/actions/list.md index 49874775..76e5e987 100644 --- a/docs/man/policy/actions/list.md +++ b/docs/man/policy/actions/list.md @@ -8,7 +8,6 @@ command: - name: namespace shorthand: s description: Namespace ID or FQN - required: true - name: limit shorthand: l description: Limit retrieved count diff --git a/e2e/actions.bats b/e2e/actions.bats index 70497d10..a8c44ac5 100644 --- a/e2e/actions.bats +++ b/e2e/actions.bats @@ -120,6 +120,15 @@ teardown_file() { assert_output --partial "delete" total=$(echo "$output" | jq -r '.pagination.total') [[ "$total" -ge 1 ]] + + # listing without namespace should succeed + run_otdfctl_action list + assert_output --partial "create" + assert_output --partial "read" + assert_output --partial "update" + assert_output --partial "delete" + assert_output --partial "Total" + assert_line --regexp "Current Offset.*0" } @test "Update action" { From e55aa94f2874cb8bb90b1aa0a5d93bd7f25a46e8 Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Fri, 13 Mar 2026 13:10:44 -0400 Subject: [PATCH 13/26] add namespace to output --- cmd/policy/actions.go | 24 ++++++++++++++++++++++-- e2e/actions.bats | 8 ++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/cmd/policy/actions.go b/cmd/policy/actions.go index dd16eb6e..50d4cdc6 100644 --- a/cmd/policy/actions.go +++ b/cmd/policy/actions.go @@ -39,6 +39,7 @@ func policyGetAction(cmd *cobra.Command, args []string) { rows := [][]string{ {"Id", action.GetId()}, {"Name", action.GetName()}, + {"Namespace", action.GetNamespace().GetFqn()}, } if mdRows := getMetadataRows(action.GetMetadata()); mdRows != nil { rows = append(rows, mdRows...) @@ -65,6 +66,7 @@ func policyListActions(cmd *cobra.Command, args []string) { cli.NewUUIDColumn(), table.NewFlexColumn("name", "Name", cli.FlexColumnWidthFour), table.NewFlexColumn("action_type", "Action Type", cli.FlexColumnWidthFour), + table.NewFlexColumn("namespace", "Namespace", cli.FlexColumnWidthFour), ) rows := []table.Row{} for _, a := range resp.GetActionsStandard() { @@ -72,13 +74,22 @@ func policyListActions(cmd *cobra.Command, args []string) { "id": a.GetId(), "action_type": "standard", "name": a.GetName(), + // for standard actions we should only include the namespace if it is not null + "namespace": func() string { + if a.GetNamespace() != nil { + return a.GetNamespace().GetFqn() + } + return "" + }(), })) } + for _, a := range resp.GetActionsCustom() { rows = append(rows, table.NewRow(table.RowData{ "id": a.GetId(), "action_type": "custom", "name": a.GetName(), + "namespace": a.GetNamespace().GetFqn(), })) } @@ -104,6 +115,7 @@ func policyCreateAction(cmd *cobra.Command, args []string) { rows := [][]string{ {"Id", action.GetId()}, {"Name", action.GetName()}, + {"Namespace", action.GetNamespace().GetFqn()}, } if mdRows := getMetadataRows(action.GetMetadata()); mdRows != nil { @@ -136,7 +148,11 @@ func policyDeleteAction(cmd *cobra.Command, args []string) { errMsg := fmt.Sprintf("Failed to delete action (%s)", id) cli.ExitWithError(errMsg, err) } - rows := [][]string{{"Id", id}, {"Name", action.GetName()}} + rows := [][]string{ + {"Id", id}, + {"Name", action.GetName()}, + {"Namespace", action.GetNamespace().GetFqn()}, + } if mdRows := getMetadataRows(action.GetMetadata()); mdRows != nil { rows = append(rows, mdRows...) } @@ -163,7 +179,11 @@ func policyUpdateAction(cmd *cobra.Command, args []string) { if err != nil { cli.ExitWithError("Failed to update action", err) } - rows := [][]string{{"Id", id}, {"Name", updated.GetName()}} + rows := [][]string{ + {"Id", id}, + {"Name", updated.GetName()}, + {"Namespace", updated.GetNamespace().GetFqn()}, + } if mdRows := getMetadataRows(updated.GetMetadata()); mdRows != nil { rows = append(rows, mdRows...) } diff --git a/e2e/actions.bats b/e2e/actions.bats index a8c44ac5..8b194769 100644 --- a/e2e/actions.bats +++ b/e2e/actions.bats @@ -27,6 +27,7 @@ teardown_file() { run_otdfctl_action create --name test_action_create --namespace "$ACTION_NAMESPACE" assert_output --partial "SUCCESS" assert_line --regexp "Name.*test_action_create" + assert_line --regexp "Namespace.*$ACTION_NAMESPACE" assert_output --partial "Id" assert_output --partial "Created At" assert_line --partial "Updated At" @@ -77,6 +78,7 @@ teardown_file() { assert_success assert_line --partial "Id" assert_line --regexp "Name.*read" + assert_line --regexp "Namespace.*$ACTION_NAMESPACE" # get by name to retrieve the ID UPDATE_ACTION_ID=$(./otdfctl policy actions get --name update --namespace "$ACTION_NAMESPACE" --json $HOST $WITH_CREDS | jq -r '.id') @@ -104,6 +106,8 @@ teardown_file() { @test "List actions" { run_otdfctl_action list --namespace "$ACTION_NAMESPACE" + assert_output --partial "Namespace" + assert_output --partial "$ACTION_NAMESPACE" assert_output --partial "create" assert_output --partial "read" assert_output --partial "update" @@ -138,6 +142,7 @@ teardown_file() { assert_success assert_line --regexp "Id.*$ACTION_TO_UPDATE" assert_line --regexp "Name.*testing_updation" + assert_line --regexp "Namespace.*$ACTION_NAMESPACE" assert_line --regexp "Labels.*key: value" assert_line --regexp "Labels.*test: true" @@ -146,6 +151,7 @@ teardown_file() { assert_success assert_line --regexp "Id.*$ACTION_TO_UPDATE" assert_line --regexp "Name.*testing_updation" + assert_line --regexp "Namespace.*$ACTION_NAMESPACE" assert_line --regexp "Labels.*key: other" refute_output --regexp "Labels.*key: value" refute_output --regexp "Labels.*test: true" @@ -156,6 +162,7 @@ teardown_file() { assert_success assert_line --regexp "Id.*$ACTION_TO_UPDATE" assert_line --regexp "Name.*updated_action_in_test" + assert_line --regexp "Namespace.*$ACTION_NAMESPACE" refute_output --regexp "Name.*testing_updation" # clean up @@ -172,4 +179,5 @@ teardown_file() { DELETABLE_ACTION=$(./otdfctl policy actions create --name testing-delete --namespace "$ACTION_NAMESPACE" $HOST $WITH_CREDS --json | jq -r '.id') run_otdfctl_action delete --id "$DELETABLE_ACTION" --force assert_success + assert_line --regexp "Namespace.*$ACTION_NAMESPACE" } From 5c10bd7eb254c9c6d14b538bc5e5259a4e7bb2e1 Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Fri, 13 Mar 2026 14:30:19 -0400 Subject: [PATCH 14/26] handle possibly null namespace --- cmd/policy/actions.go | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/cmd/policy/actions.go b/cmd/policy/actions.go index 50d4cdc6..228b0468 100644 --- a/cmd/policy/actions.go +++ b/cmd/policy/actions.go @@ -39,7 +39,12 @@ func policyGetAction(cmd *cobra.Command, args []string) { rows := [][]string{ {"Id", action.GetId()}, {"Name", action.GetName()}, - {"Namespace", action.GetNamespace().GetFqn()}, + {"Namespace", func() string { + if action.GetNamespace() != nil { + return action.GetNamespace().GetFqn() + } + return "" + }()}, } if mdRows := getMetadataRows(action.GetMetadata()); mdRows != nil { rows = append(rows, mdRows...) @@ -89,7 +94,12 @@ func policyListActions(cmd *cobra.Command, args []string) { "id": a.GetId(), "action_type": "custom", "name": a.GetName(), - "namespace": a.GetNamespace().GetFqn(), + "namespace": func() string { + if a.GetNamespace() != nil { + return a.GetNamespace().GetFqn() + } + return "" + }(), })) } @@ -115,7 +125,12 @@ func policyCreateAction(cmd *cobra.Command, args []string) { rows := [][]string{ {"Id", action.GetId()}, {"Name", action.GetName()}, - {"Namespace", action.GetNamespace().GetFqn()}, + {"Namespace", func() string { + if action.GetNamespace() != nil { + return action.GetNamespace().GetFqn() + } + return "" + }()}, } if mdRows := getMetadataRows(action.GetMetadata()); mdRows != nil { @@ -151,7 +166,12 @@ func policyDeleteAction(cmd *cobra.Command, args []string) { rows := [][]string{ {"Id", id}, {"Name", action.GetName()}, - {"Namespace", action.GetNamespace().GetFqn()}, + {"Namespace", func() string { + if action.GetNamespace() != nil { + return action.GetNamespace().GetFqn() + } + return "" + }()}, } if mdRows := getMetadataRows(action.GetMetadata()); mdRows != nil { rows = append(rows, mdRows...) @@ -182,7 +202,12 @@ func policyUpdateAction(cmd *cobra.Command, args []string) { rows := [][]string{ {"Id", id}, {"Name", updated.GetName()}, - {"Namespace", updated.GetNamespace().GetFqn()}, + {"Namespace", func() string { + if updated.GetNamespace() != nil { + return updated.GetNamespace().GetFqn() + } + return "" + }()}, } if mdRows := getMetadataRows(updated.GetMetadata()); mdRows != nil { rows = append(rows, mdRows...) From 2b2f5986025f53db523be9b4527c77c4da172d51 Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Tue, 24 Mar 2026 17:26:40 -0400 Subject: [PATCH 15/26] change ref back --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e1616a49..28266de0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -59,7 +59,7 @@ jobs: - name: Start up the platform with deps and containers uses: opentdf/platform/test/start-up-with-containers@main with: - platform-ref: "dspx-2450-list-actions-shouldnt-require-namespace" + platform-ref: "main" - uses: opentdf/otdfctl/e2e@main with: otdfctl-ref: ${{ github.event.pull_request.head.sha }} From 47301273001d8c6effc9862eb1d182dd50bf488b Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Tue, 24 Mar 2026 17:32:04 -0400 Subject: [PATCH 16/26] optional on create --- cmd/policy/actions.go | 2 +- docs/man/policy/actions/create.md | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/policy/actions.go b/cmd/policy/actions.go index 228b0468..2bbc1fd2 100644 --- a/cmd/policy/actions.go +++ b/cmd/policy/actions.go @@ -114,7 +114,7 @@ func policyCreateAction(cmd *cobra.Command, args []string) { defer h.Close() name := c.Flags.GetRequiredString("name") - namespace := c.Flags.GetRequiredString("namespace") + namespace := c.Flags.GetOptionalString("namespace") metadataLabels = c.Flags.GetStringSlice("label", metadataLabels, cli.FlagsStringSliceOptions{Min: 0}) action, err := h.CreateAction(cmd.Context(), name, namespace, getMetadataMutable(metadataLabels)) diff --git a/docs/man/policy/actions/create.md b/docs/man/policy/actions/create.md index 312dff93..b20c6650 100644 --- a/docs/man/policy/actions/create.md +++ b/docs/man/policy/actions/create.md @@ -14,7 +14,6 @@ command: - name: namespace shorthand: s description: Namespace ID or FQN - required: true - name: label description: "Optional metadata 'labels' in the format: key=value" shorthand: l From e8ae5f1819728a3b96d5666881937090e19811ec Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Tue, 24 Mar 2026 17:49:26 -0400 Subject: [PATCH 17/26] suggestions --- e2e/actions.bats | 13 ++++++++----- e2e/registered-resources.bats | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/e2e/actions.bats b/e2e/actions.bats index 8b194769..fe8d1458 100644 --- a/e2e/actions.bats +++ b/e2e/actions.bats @@ -51,9 +51,10 @@ teardown_file() { assert_failure assert_output --partial "Flag '--name' is required" - run_otdfctl_action create --name no_namespace - assert_failure - assert_output --partial "Flag '--namespace' is required" + # TODO: re-enable when namespace is optional + # run_otdfctl_action create --name no_namespace + # assert_failure + # assert_output --partial "Flag '--namespace' is required" # conflict run_otdfctl_action create -n "read" --namespace "$ACTION_NAMESPACE" @@ -61,9 +62,11 @@ teardown_file() { assert_output --partial "intended action would violate a restriction" # duplicate custom action - run_otdfctl_action create --name test_action_conflict --namespace "$ACTION_NAMESPACE" + run_otdfctl_action create --name test_action_conflict --namespace "$ACTION_NAMESPACE" --json assert_success - conflict_action_id=$(echo "$output" | grep Id | awk -F'│' '{print $3}' | xargs) + conflict_action_id=$(echo "$output" | jq -er '.id') + assert_success + [ -n "$conflict_action_id" ] run_otdfctl_action create --name test_action_conflict --namespace "$ACTION_NAMESPACE" assert_failure diff --git a/e2e/registered-resources.bats b/e2e/registered-resources.bats index 14e1c0fb..ec663418 100644 --- a/e2e/registered-resources.bats +++ b/e2e/registered-resources.bats @@ -49,7 +49,7 @@ teardown_file() { ./otdfctl $HOST $WITH_CREDS policy registered-resources delete --id "$RR_ID" --force # remove the custom action used in registered resource values tests - ./otdfctl $HOST $WITH_CREDS policy actions delete --id "$CUSTOM_ACTION_ID" --namespace "$NS_ID" --force + ./otdfctl $HOST $WITH_CREDS policy actions delete --id "$CUSTOM_ACTION_ID" --force # remove the namespace and cascade delete attributes and values used in registered resource values tests ./otdfctl $HOST $WITH_CREDS policy attributes namespaces unsafe delete --id "$NS_ID" --force From 1a1be8761aef0d7ae97bce7ef11f12db37d3b546 Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Tue, 24 Mar 2026 17:54:22 -0400 Subject: [PATCH 18/26] coderabbit suggestions --- cmd/policy/actions.go | 7 ++++--- e2e/actions.bats | 7 ++++--- e2e/registered-resources.bats | 6 ++++-- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/cmd/policy/actions.go b/cmd/policy/actions.go index 2bbc1fd2..23772003 100644 --- a/cmd/policy/actions.go +++ b/cmd/policy/actions.go @@ -22,9 +22,10 @@ func policyGetAction(cmd *cobra.Command, args []string) { if id == "" && name == "" { cli.ExitWithError("Either 'id' or 'name' must be provided", nil) } - if id == "" && name != "" && namespace == "" { - cli.ExitWithError("'namespace' must be provided when using 'name'", nil) - } + // TODO: re-enable when namespace is required + // if id == "" && name != "" && namespace == "" { + // cli.ExitWithError("'namespace' must be provided when using 'name'", nil) + // } action, err := h.GetAction(cmd.Context(), id, name, namespace) if err != nil { diff --git a/e2e/actions.bats b/e2e/actions.bats index fe8d1458..8dee69bb 100644 --- a/e2e/actions.bats +++ b/e2e/actions.bats @@ -102,9 +102,10 @@ teardown_file() { assert_failure assert_output --partial "must be a valid UUID" - run_otdfctl_action get --name 'testing_get' - assert_failure - assert_output --partial "namespace' must be provided when using 'name'" + # TODO: re-enable when namespace is required + # run_otdfctl_action get --name 'testing_get' + # assert_failure + # assert_output --partial "namespace' must be provided when using 'name'" } @test "List actions" { diff --git a/e2e/registered-resources.bats b/e2e/registered-resources.bats index ec663418..5e43f666 100644 --- a/e2e/registered-resources.bats +++ b/e2e/registered-resources.bats @@ -16,11 +16,13 @@ setup_file() { # create custom action to be used in registered resource values tests export CUSTOM_ACTION_NAME="test_action_for_values" - export CUSTOM_ACTION_ID=$(./otdfctl $HOST $WITH_CREDS policy actions create --name "$CUSTOM_ACTION_NAME" --namespace "$NS_ID" --json | jq -r '.id') + CUSTOM_ACTION_ID=$(./otdfctl $HOST $WITH_CREDS policy actions create --name "$CUSTOM_ACTION_NAME" --namespace "$NS_ID" --json | jq -r '.id') + export CUSTOM_ACTION_ID # get standard read action id to use in registered resource values tests export READ_ACTION_NAME="read" - export READ_ACTION_ID=$(./otdfctl $HOST $WITH_CREDS policy actions get --name "$READ_ACTION_NAME" --namespace "$NS_ID" --json | jq -r '.id') + READ_ACTION_ID=$(./otdfctl $HOST $WITH_CREDS policy actions get --name "$READ_ACTION_NAME" --namespace "$NS_ID" --json | jq -r '.id') + export READ_ACTION_ID export ATTR_NAME=test_rr_attr attr_id=$(./otdfctl $HOST $WITH_CREDS policy attributes create --namespace "$NS_ID" --name "$ATTR_NAME" --rule ANY_OF -l key=value --json | jq -r '.id') export ATTR_VAL_1_ID=$(./otdfctl $HOST $WITH_CREDS policy attributes values create --attribute-id "$attr_id" --value test_reg_res_attr__val_1 --json | jq -r '.id') From 970e9052be7e69e710c937b267a7ecb8becda69b Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Tue, 24 Mar 2026 18:12:16 -0400 Subject: [PATCH 19/26] some bats fixes --- e2e/actions.bats | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/e2e/actions.bats b/e2e/actions.bats index 8dee69bb..6d441075 100644 --- a/e2e/actions.bats +++ b/e2e/actions.bats @@ -5,7 +5,10 @@ setup_file() { export WITH_CREDS='--with-client-creds-file ./creds.json' export HOST='--host http://localhost:8080' - export ACTION_NAMESPACE='https://example.com' + export ACTION_NAMESPACE_NAME='test-act.org' + export ACTION_NAMESPACE="https://$ACTION_NAMESPACE_NAME" + # create namespace first (needed for action creation) + export NS_ID=$(./otdfctl $HOST $WITH_CREDS policy attributes namespaces create --name "$ACTION_NAMESPACE_NAME" --json | jq -r '.id') } setup() { @@ -20,9 +23,12 @@ setup() { teardown_file() { # clear out all test env vars - unset HOST WITH_CREDS ACTION_NAMESPACE + # remove the namespace and cascade delete attributes and values used in registered resource values tests + ./otdfctl $HOST $WITH_CREDS policy attributes namespaces unsafe delete --id "$NS_ID" --force + unset HOST WITH_CREDS ACTION_NAMESPACE ACTION_NAMESPACE_NAME NS_ID } + @test "Create a new custom action - Good" { run_otdfctl_action create --name test_action_create --namespace "$ACTION_NAMESPACE" assert_output --partial "SUCCESS" @@ -91,6 +97,13 @@ teardown_file() { assert_success [ "$(echo "$output" | jq -r '.id')" = "$UPDATE_ACTION_ID" ] [ "$(echo "$output" | jq -r '.name')" = "update" ] + + # ensure you can use the namespace id instead of the fqn + run_otdfctl_action get --name "read" --namespace "$NS_ID" + assert_success + assert_line --partial "Id" + assert_line --regexp "Name.*read" + assert_line --regexp "Namespace.*$ACTION_NAMESPACE" } @test "Get an action - Bad" { From 2c9caa6c7ab564e0f56781b8c192774cf547966e Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Tue, 24 Mar 2026 18:35:32 -0400 Subject: [PATCH 20/26] coderabbit suggestion --- e2e/obligations.bats | 4 ++-- e2e/registered-resources.bats | 18 ++++++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/e2e/obligations.bats b/e2e/obligations.bats index 07a4d1f4..fdbc9fd9 100644 --- a/e2e/obligations.bats +++ b/e2e/obligations.bats @@ -22,9 +22,9 @@ setup_file() { # create shared actions for tests export ACTION_1_NAME="test_action_1" - export ACTION_1_ID=$(./otdfctl $HOST $WITH_CREDS policy actions create --name "$ACTION_1_NAME" --json | jq -r '.id') + export ACTION_1_ID=$(./otdfctl $HOST $WITH_CREDS policy actions create --name "$ACTION_1_NAME" --namespace "$NS_ID" --json | jq -r '.id') export ACTION_2_NAME="test_action_2" - export ACTION_2_ID=$(./otdfctl $HOST $WITH_CREDS policy actions create --name "$ACTION_2_NAME" --json | jq -r '.id') + export ACTION_2_ID=$(./otdfctl $HOST $WITH_CREDS policy actions create --name "$ACTION_2_NAME" --namespace "$NS_ID" --json | jq -r '.id') # create shared attributes for tests export ATTR_NAME="test_attr_for_triggers" diff --git a/e2e/registered-resources.bats b/e2e/registered-resources.bats index 5e43f666..b5492499 100644 --- a/e2e/registered-resources.bats +++ b/e2e/registered-resources.bats @@ -8,11 +8,13 @@ setup_file() { # create namespace first (needed for registered resource creation) export NS_NAME="test-rr.org" - export NS_ID=$(./otdfctl $HOST $WITH_CREDS policy attributes namespaces create --name "$NS_NAME" --json | jq -r '.id') + NS_ID=$(./otdfctl $HOST $WITH_CREDS policy attributes namespaces create --name "$NS_NAME" --json | jq -r '.id') + export NS_ID # create registered resource used in registered resource values tests export RR_NAME="test_rr_for_values" - export RR_ID=$(./otdfctl $HOST $WITH_CREDS policy registered-resources create --name "$RR_NAME" --namespace "$NS_ID" --json | jq -r '.id') + RR_ID=$(./otdfctl $HOST $WITH_CREDS policy registered-resources create --name "$RR_NAME" --namespace "$NS_ID" --json | jq -r '.id') + export RR_ID # create custom action to be used in registered resource values tests export CUSTOM_ACTION_NAME="test_action_for_values" @@ -25,10 +27,14 @@ setup_file() { export READ_ACTION_ID export ATTR_NAME=test_rr_attr attr_id=$(./otdfctl $HOST $WITH_CREDS policy attributes create --namespace "$NS_ID" --name "$ATTR_NAME" --rule ANY_OF -l key=value --json | jq -r '.id') - export ATTR_VAL_1_ID=$(./otdfctl $HOST $WITH_CREDS policy attributes values create --attribute-id "$attr_id" --value test_reg_res_attr__val_1 --json | jq -r '.id') - export ATTR_VAL_1_FQN=$(./otdfctl $HOST $WITH_CREDS policy attributes values get --id "$ATTR_VAL_1_ID" --json | jq -r '.fqn') - export ATTR_VAL_2_ID=$(./otdfctl $HOST $WITH_CREDS policy attributes values create --attribute-id "$attr_id" --value test_reg_res_attr__val_2 --json | jq -r '.id') - export ATTR_VAL_2_FQN=$(./otdfctl $HOST $WITH_CREDS policy attributes values get --id "$ATTR_VAL_2_ID" --json | jq -r '.fqn') + ATTR_VAL_1_ID=$(./otdfctl $HOST $WITH_CREDS policy attributes values create --attribute-id "$attr_id" --value test_reg_res_attr__val_1 --json | jq -r '.id') + export ATTR_VAL_1_ID + ATTR_VAL_1_FQN=$(./otdfctl $HOST $WITH_CREDS policy attributes values get --id "$ATTR_VAL_1_ID" --json | jq -r '.fqn') + export ATTR_VAL_1_FQN + ATTR_VAL_2_ID=$(./otdfctl $HOST $WITH_CREDS policy attributes values create --attribute-id "$attr_id" --value test_reg_res_attr__val_2 --json | jq -r '.id') + export ATTR_VAL_2_ID + ATTR_VAL_2_FQN=$(./otdfctl $HOST $WITH_CREDS policy attributes values get --id "$ATTR_VAL_2_ID" --json | jq -r '.fqn') + export ATTR_VAL_2_FQN echo "FQN: $ATTR_VAL_1_FQN" } From 1020eeefb2302cdc684b9b5c9eee6c0422d991ab Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Tue, 24 Mar 2026 18:46:45 -0400 Subject: [PATCH 21/26] debugging --- e2e/registered-resources.bats | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/e2e/registered-resources.bats b/e2e/registered-resources.bats index b5492499..1ccdc3cd 100644 --- a/e2e/registered-resources.bats +++ b/e2e/registered-resources.bats @@ -19,11 +19,19 @@ setup_file() { # create custom action to be used in registered resource values tests export CUSTOM_ACTION_NAME="test_action_for_values" CUSTOM_ACTION_ID=$(./otdfctl $HOST $WITH_CREDS policy actions create --name "$CUSTOM_ACTION_NAME" --namespace "$NS_ID" --json | jq -r '.id') + if [ -z "$CUSTOM_ACTION_ID" ]; then + echo "Failed to resolve custom action id" + exit 1 + fi export CUSTOM_ACTION_ID # get standard read action id to use in registered resource values tests export READ_ACTION_NAME="read" READ_ACTION_ID=$(./otdfctl $HOST $WITH_CREDS policy actions get --name "$READ_ACTION_NAME" --namespace "$NS_ID" --json | jq -r '.id') + if [ -z "$READ_ACTION_ID" ]; then + echo "Failed to resolve standard read action id" + exit 1 + fi export READ_ACTION_ID export ATTR_NAME=test_rr_attr attr_id=$(./otdfctl $HOST $WITH_CREDS policy attributes create --namespace "$NS_ID" --name "$ATTR_NAME" --rule ANY_OF -l key=value --json | jq -r '.id') From 94feb4195dae3a42a7b584a69982d6df7333f384 Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Tue, 24 Mar 2026 19:08:11 -0400 Subject: [PATCH 22/26] fix test --- e2e/registered-resources.bats | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/e2e/registered-resources.bats b/e2e/registered-resources.bats index 1ccdc3cd..8cf046a8 100644 --- a/e2e/registered-resources.bats +++ b/e2e/registered-resources.bats @@ -19,19 +19,12 @@ setup_file() { # create custom action to be used in registered resource values tests export CUSTOM_ACTION_NAME="test_action_for_values" CUSTOM_ACTION_ID=$(./otdfctl $HOST $WITH_CREDS policy actions create --name "$CUSTOM_ACTION_NAME" --namespace "$NS_ID" --json | jq -r '.id') - if [ -z "$CUSTOM_ACTION_ID" ]; then - echo "Failed to resolve custom action id" - exit 1 - fi export CUSTOM_ACTION_ID # get standard read action id to use in registered resource values tests + # TODO: when RRs support passing the namespace down to the action, add --namespace "$NS_ID" export READ_ACTION_NAME="read" - READ_ACTION_ID=$(./otdfctl $HOST $WITH_CREDS policy actions get --name "$READ_ACTION_NAME" --namespace "$NS_ID" --json | jq -r '.id') - if [ -z "$READ_ACTION_ID" ]; then - echo "Failed to resolve standard read action id" - exit 1 - fi + READ_ACTION_ID=$(./otdfctl $HOST $WITH_CREDS policy actions get --name "$READ_ACTION_NAME" --json | jq -r '.id') export READ_ACTION_ID export ATTR_NAME=test_rr_attr attr_id=$(./otdfctl $HOST $WITH_CREDS policy attributes create --namespace "$NS_ID" --name "$ATTR_NAME" --rule ANY_OF -l key=value --json | jq -r '.id') @@ -259,7 +252,8 @@ teardown_file() { created_id_simple_by_res_name=$(echo "$output" | grep Id | awk -F'│' '{print $3}' | xargs) # with action attribute values - run_otdfctl_reg_res_values create --resource "$RR_ID" --value test_create_rr_val_with_action_attr_vals --action-attribute-value "\"$READ_ACTION_ID;$ATTR_VAL_1_FQN\"" --action-attribute-value "\"$CUSTOM_ACTION_NAME;$ATTR_VAL_2_ID\"" --json + # TODO(namespaced-actions): switch custom action identifier back to name when RR action resolution is namespaced. + run_otdfctl_reg_res_values create --resource "$RR_ID" --value test_create_rr_val_with_action_attr_vals --action-attribute-value "\"$READ_ACTION_ID;$ATTR_VAL_1_FQN\"" --action-attribute-value "\"$CUSTOM_ACTION_ID;$ATTR_VAL_2_ID\"" --json assert_success [ "$(echo "$output" | jq -r '.id')" != "" ] [ "$(echo "$output" | jq -r '.value')" = "test_create_rr_val_with_action_attr_vals" ] @@ -437,7 +431,8 @@ teardown_file() { [ "$(echo "$output" | jq -r 'any(.action_attribute_values[]; .action.id == "'"$READ_ACTION_ID"'" and .action.name == "'"$READ_ACTION_NAME"'" and .attribute_value.id == "'"$ATTR_VAL_1_ID"'" and .attribute_value.fqn == "'"$ATTR_VAL_1_FQN"'")')" = "true" ] # update action attribute values - run_otdfctl_reg_res_values update --id "$created_id" --action-attribute-value "\"$READ_ACTION_NAME;$ATTR_VAL_1_FQN\"" --action-attribute-value "\"$CUSTOM_ACTION_ID;$ATTR_VAL_2_ID\"" --force --json + # TODO(namespaced-actions): switch action identifiers back to names when RR action resolution is namespaced. + run_otdfctl_reg_res_values update --id "$created_id" --action-attribute-value "\"$READ_ACTION_ID;$ATTR_VAL_1_FQN\"" --action-attribute-value "\"$CUSTOM_ACTION_ID;$ATTR_VAL_2_ID\"" --force --json assert_success [ "$(echo "$output" | jq -r '.id')" = "$created_id" ] [ "$(echo "$output" | jq -r 'any(.action_attribute_values[]; .action.id == "'"$READ_ACTION_ID"'" and .action.name == "'"$READ_ACTION_NAME"'" and .attribute_value.id == "'"$ATTR_VAL_1_ID"'" and .attribute_value.fqn == "'"$ATTR_VAL_1_FQN"'")')" = "true" ] From 19f88813a30b73b8fde53063623c1290194590bc Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Wed, 25 Mar 2026 10:48:15 -0400 Subject: [PATCH 23/26] review suggestions --- cmd/policy/actions.go | 27 ++++--------------------- e2e/actions.bats | 31 ++++++++++++++++++++++++++--- pkg/handlers/actions.go | 8 -------- pkg/handlers/namespaces.go | 7 +++++++ pkg/handlers/obligations.go | 22 +++----------------- pkg/handlers/registeredResources.go | 22 +++----------------- 6 files changed, 45 insertions(+), 72 deletions(-) diff --git a/cmd/policy/actions.go b/cmd/policy/actions.go index 23772003..67c0e0b6 100644 --- a/cmd/policy/actions.go +++ b/cmd/policy/actions.go @@ -17,15 +17,12 @@ func policyGetAction(cmd *cobra.Command, args []string) { id := c.Flags.GetOptionalID("id") name := c.Flags.GetOptionalString("name") + // TODO: switch to required namespace if id not provided once namespacing is required by policy namespace := c.Flags.GetOptionalString("namespace") if id == "" && name == "" { cli.ExitWithError("Either 'id' or 'name' must be provided", nil) } - // TODO: re-enable when namespace is required - // if id == "" && name != "" && namespace == "" { - // cli.ExitWithError("'namespace' must be provided when using 'name'", nil) - // } action, err := h.GetAction(cmd.Context(), id, name, namespace) if err != nil { @@ -40,12 +37,7 @@ func policyGetAction(cmd *cobra.Command, args []string) { rows := [][]string{ {"Id", action.GetId()}, {"Name", action.GetName()}, - {"Namespace", func() string { - if action.GetNamespace() != nil { - return action.GetNamespace().GetFqn() - } - return "" - }()}, + {"Namespace", action.GetNamespace().GetFqn()}, } if mdRows := getMetadataRows(action.GetMetadata()); mdRows != nil { rows = append(rows, mdRows...) @@ -80,13 +72,7 @@ func policyListActions(cmd *cobra.Command, args []string) { "id": a.GetId(), "action_type": "standard", "name": a.GetName(), - // for standard actions we should only include the namespace if it is not null - "namespace": func() string { - if a.GetNamespace() != nil { - return a.GetNamespace().GetFqn() - } - return "" - }(), + "namespace": a.GetNamespace().GetFqn(), })) } @@ -95,12 +81,7 @@ func policyListActions(cmd *cobra.Command, args []string) { "id": a.GetId(), "action_type": "custom", "name": a.GetName(), - "namespace": func() string { - if a.GetNamespace() != nil { - return a.GetNamespace().GetFqn() - } - return "" - }(), + "namespace": a.GetNamespace().GetFqn(), })) } diff --git a/e2e/actions.bats b/e2e/actions.bats index 6d441075..5c14d947 100644 --- a/e2e/actions.bats +++ b/e2e/actions.bats @@ -30,9 +30,10 @@ teardown_file() { @test "Create a new custom action - Good" { - run_otdfctl_action create --name test_action_create --namespace "$ACTION_NAMESPACE" + # with a namespace + run_otdfctl_action create --name test_action_create_namespaced --namespace "$ACTION_NAMESPACE" assert_output --partial "SUCCESS" - assert_line --regexp "Name.*test_action_create" + assert_line --regexp "Name.*test_action_create_namespaced" assert_line --regexp "Namespace.*$ACTION_NAMESPACE" assert_output --partial "Id" assert_output --partial "Created At" @@ -41,6 +42,19 @@ teardown_file() { # cleanup created_id=$(echo "$output" | grep Id | awk -F'│' '{print $3}' | xargs) run_otdfctl_action delete --id $created_id --force + + # without a namespace (should default to un-namespaced) + run_otdfctl_action create --name test_action_create + assert_output --partial "SUCCESS" + assert_line --regexp "Name.*test_action_create" + assert_output --partial "Id" + assert_output --partial "Created At" + assert_line --partial "Updated At" + # ensure namespace is empty for un-namespaced actions + refute_line --regexp "Namespace.*$ACTION_NAMESPACE" + + created_id=$(echo "$output" | grep Id | awk -F'│' '{print $3}' | xargs) + run_otdfctl_action delete --id $created_id --force } @test "Create a new action - Bad" { @@ -57,7 +71,7 @@ teardown_file() { assert_failure assert_output --partial "Flag '--name' is required" - # TODO: re-enable when namespace is optional + # TODO: re-enable when namespace is required # run_otdfctl_action create --name no_namespace # assert_failure # assert_output --partial "Flag '--namespace' is required" @@ -66,6 +80,10 @@ teardown_file() { run_otdfctl_action create -n "read" --namespace "$ACTION_NAMESPACE" assert_failure assert_output --partial "intended action would violate a restriction" + + run_otdfctl_action create -n "read" + assert_failure + assert_output --partial "intended action would violate a restriction" # duplicate custom action run_otdfctl_action create --name test_action_conflict --namespace "$ACTION_NAMESPACE" --json @@ -104,6 +122,13 @@ teardown_file() { assert_line --partial "Id" assert_line --regexp "Name.*read" assert_line --regexp "Namespace.*$ACTION_NAMESPACE" + + # ensure get without namespace still works for un-namespaced actions + run_otdfctl_action get --name "read" + assert_success + assert_line --partial "Id" + assert_line --regexp "Name.*read" + refute_line --regexp "Namespace.*$ACTION_NAMESPACE" } @test "Get an action - Bad" { diff --git a/pkg/handlers/actions.go b/pkg/handlers/actions.go index 1e95927e..7f8db58d 100644 --- a/pkg/handlers/actions.go +++ b/pkg/handlers/actions.go @@ -3,19 +3,11 @@ package handlers import ( "context" - "github.com/google/uuid" "github.com/opentdf/platform/protocol/go/common" "github.com/opentdf/platform/protocol/go/policy" "github.com/opentdf/platform/protocol/go/policy/actions" ) -func getNamespaceIDAndFQN(namespace string) (string, string) { - if _, err := uuid.Parse(namespace); err != nil { - return "", namespace - } - return namespace, "" -} - func (h Handler) GetAction(ctx context.Context, id string, name string, namespace string) (*policy.Action, error) { req := &actions.GetActionRequest{} if id != "" { diff --git a/pkg/handlers/namespaces.go b/pkg/handlers/namespaces.go index 4df71f81..6cc2284c 100644 --- a/pkg/handlers/namespaces.go +++ b/pkg/handlers/namespaces.go @@ -11,6 +11,13 @@ import ( "github.com/opentdf/platform/protocol/go/policy/unsafe" ) +func getNamespaceIDAndFQN(namespace string) (string, string) { + if _, err := uuid.Parse(namespace); err != nil { + return "", namespace + } + return namespace, "" +} + func (h Handler) GetNamespace(ctx context.Context, identifier string) (*policy.Namespace, error) { req := &namespaces.GetNamespaceRequest{ Identifier: &namespaces.GetNamespaceRequest_NamespaceId{ diff --git a/pkg/handlers/obligations.go b/pkg/handlers/obligations.go index 5de0d61c..59373ae2 100644 --- a/pkg/handlers/obligations.go +++ b/pkg/handlers/obligations.go @@ -37,13 +37,7 @@ func (h Handler) CreateObligation(ctx context.Context, namespace, name string, v Values: values, Metadata: metadata, } - - _, err := uuid.Parse(namespace) - if err != nil { - req.NamespaceFqn = namespace - } else { - req.NamespaceId = namespace - } + req.NamespaceId, req.NamespaceFqn = getNamespaceIDAndFQN(namespace) resp, err := h.sdk.Obligations.CreateObligation(ctx, req) if err != nil { @@ -77,12 +71,7 @@ func (h Handler) ListObligations(ctx context.Context, limit, offset int32, names }, } if namespace != "" { - _, err := uuid.Parse(namespace) - if err != nil { - req.NamespaceFqn = namespace - } else { - req.NamespaceId = namespace - } + req.NamespaceId, req.NamespaceFqn = getNamespaceIDAndFQN(namespace) } return h.sdk.Obligations.ListObligations(ctx, req) } @@ -237,12 +226,7 @@ func (h Handler) ListObligationTriggers(ctx context.Context, namespace string, l } if namespace != "" { - _, err := uuid.Parse(namespace) - if err != nil { - req.NamespaceFqn = namespace - } else { - req.NamespaceId = namespace - } + req.NamespaceId, req.NamespaceFqn = getNamespaceIDAndFQN(namespace) } return h.sdk.Obligations.ListObligationTriggers(ctx, req) diff --git a/pkg/handlers/registeredResources.go b/pkg/handlers/registeredResources.go index 19b65a1f..cbbcc74e 100644 --- a/pkg/handlers/registeredResources.go +++ b/pkg/handlers/registeredResources.go @@ -3,7 +3,6 @@ package handlers import ( "context" - "github.com/google/uuid" "github.com/opentdf/platform/protocol/go/common" "github.com/opentdf/platform/protocol/go/policy" "github.com/opentdf/platform/protocol/go/policy/registeredresources" @@ -20,12 +19,7 @@ func (h Handler) CreateRegisteredResource(ctx context.Context, namespace, name s Metadata: metadata, } - _, err := uuid.Parse(namespace) - if err != nil { - req.NamespaceFqn = namespace - } else { - req.NamespaceId = namespace - } + req.NamespaceId, req.NamespaceFqn = getNamespaceIDAndFQN(namespace) resp, err := h.sdk.RegisteredResources.CreateRegisteredResource(ctx, req) if err != nil { @@ -47,12 +41,7 @@ func (h Handler) GetRegisteredResource(ctx context.Context, id, name, namespace } } if namespace != "" { - _, err := uuid.Parse(namespace) - if err != nil { - req.NamespaceFqn = namespace - } else { - req.NamespaceId = namespace - } + req.NamespaceId, req.NamespaceFqn = getNamespaceIDAndFQN(namespace) } resp, err := h.sdk.RegisteredResources.GetRegisteredResource(ctx, req) @@ -71,12 +60,7 @@ func (h Handler) ListRegisteredResources(ctx context.Context, limit, offset int3 }, } if namespace != "" { - _, err := uuid.Parse(namespace) - if err != nil { - req.NamespaceFqn = namespace - } else { - req.NamespaceId = namespace - } + req.NamespaceId, req.NamespaceFqn = getNamespaceIDAndFQN(namespace) } return h.sdk.RegisteredResources.ListRegisteredResources(ctx, req) } From 7eb387639f9a897e16e48bd67b3a8cfd57fdb094 Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Wed, 25 Mar 2026 11:11:58 -0400 Subject: [PATCH 24/26] better list test --- e2e/actions.bats | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/e2e/actions.bats b/e2e/actions.bats index 5c14d947..4d85ddac 100644 --- a/e2e/actions.bats +++ b/e2e/actions.bats @@ -147,6 +147,13 @@ teardown_file() { } @test "List actions" { + run_otdfctl_action create --name test_action_list_namespaced --namespace "$ACTION_NAMESPACE" --json + assert_success + created_id=$(echo "$output" | jq -r '.id') + run_otdfctl_action create --name test_action_list_unnamespaced --json + assert_success + created_id_2=$(echo "$output" | jq -r '.id') + run_otdfctl_action list --namespace "$ACTION_NAMESPACE" assert_output --partial "Namespace" assert_output --partial "$ACTION_NAMESPACE" @@ -156,6 +163,8 @@ teardown_file() { assert_output --partial "delete" assert_output --partial "Total" assert_line --regexp "Current Offset.*0" + assert_output --partial "test_action_list_namespaced" + refute_output --partial "test_action_list_unnamespaced" run_otdfctl_action list --namespace "$ACTION_NAMESPACE" --json assert_success @@ -167,7 +176,7 @@ teardown_file() { total=$(echo "$output" | jq -r '.pagination.total') [[ "$total" -ge 1 ]] - # listing without namespace should succeed + # listing without namespace should succeed and should not include namespaced actions run_otdfctl_action list assert_output --partial "create" assert_output --partial "read" @@ -175,6 +184,11 @@ teardown_file() { assert_output --partial "delete" assert_output --partial "Total" assert_line --regexp "Current Offset.*0" + assert_output --partial "test_action_list_namespaced" + assert_output --partial "test_action_list_unnamespaced" + + run_otdfctl_action delete --id $created_id --force + run_otdfctl_action delete --id $created_id_2 --force } @test "Update action" { From 8c13444e2f76e0ea4a9411920eb122cae46f9304 Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Wed, 25 Mar 2026 11:21:26 -0400 Subject: [PATCH 25/26] update comment --- e2e/actions.bats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/actions.bats b/e2e/actions.bats index 4d85ddac..95411cc2 100644 --- a/e2e/actions.bats +++ b/e2e/actions.bats @@ -176,7 +176,7 @@ teardown_file() { total=$(echo "$output" | jq -r '.pagination.total') [[ "$total" -ge 1 ]] - # listing without namespace should succeed and should not include namespaced actions + # listing without namespace should succeed and should include both namespaced and un-namespaced actions (namespace field should be empty for un-namespaced actions) run_otdfctl_action list assert_output --partial "create" assert_output --partial "read" From 43daf53f86cbe0e2dc201e0b6cc0d0f7039ed667 Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Wed, 25 Mar 2026 11:30:15 -0400 Subject: [PATCH 26/26] fix namespace chaining --- cmd/policy/actions.go | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/cmd/policy/actions.go b/cmd/policy/actions.go index 67c0e0b6..fcd5213b 100644 --- a/cmd/policy/actions.go +++ b/cmd/policy/actions.go @@ -107,12 +107,7 @@ func policyCreateAction(cmd *cobra.Command, args []string) { rows := [][]string{ {"Id", action.GetId()}, {"Name", action.GetName()}, - {"Namespace", func() string { - if action.GetNamespace() != nil { - return action.GetNamespace().GetFqn() - } - return "" - }()}, + {"Namespace", action.GetNamespace().GetFqn()}, } if mdRows := getMetadataRows(action.GetMetadata()); mdRows != nil { @@ -148,12 +143,7 @@ func policyDeleteAction(cmd *cobra.Command, args []string) { rows := [][]string{ {"Id", id}, {"Name", action.GetName()}, - {"Namespace", func() string { - if action.GetNamespace() != nil { - return action.GetNamespace().GetFqn() - } - return "" - }()}, + {"Namespace", action.GetNamespace().GetFqn()}, } if mdRows := getMetadataRows(action.GetMetadata()); mdRows != nil { rows = append(rows, mdRows...) @@ -184,12 +174,7 @@ func policyUpdateAction(cmd *cobra.Command, args []string) { rows := [][]string{ {"Id", id}, {"Name", updated.GetName()}, - {"Namespace", func() string { - if updated.GetNamespace() != nil { - return updated.GetNamespace().GetFqn() - } - return "" - }()}, + {"Namespace", updated.GetNamespace().GetFqn()}, } if mdRows := getMetadataRows(updated.GetMetadata()); mdRows != nil { rows = append(rows, mdRows...)