diff --git a/docs/api/openapi.yml b/docs/api/openapi.yml index b4615b13265..79dd3f74a3e 100644 --- a/docs/api/openapi.yml +++ b/docs/api/openapi.yml @@ -2559,6 +2559,7 @@ components: - factor-api-key - license - subscription + - deployment-host - logs - traces - metrics @@ -23942,9 +23943,9 @@ paths: description: Internal Server Error security: - api_key: - - VIEWER + - deployment-host:list - tokenizer: - - VIEWER + - deployment-host:list summary: Get host info from Zeus. tags: - zeus @@ -23998,9 +23999,9 @@ paths: description: Internal Server Error security: - api_key: - - ADMIN + - deployment-host:update - tokenizer: - - ADMIN + - deployment-host:update summary: Put host in Zeus for a deployment. tags: - zeus diff --git a/frontend/src/api/generated/services/sigNoz.schemas.ts b/frontend/src/api/generated/services/sigNoz.schemas.ts index 2a80cf0f3f7..19404b3b037 100644 --- a/frontend/src/api/generated/services/sigNoz.schemas.ts +++ b/frontend/src/api/generated/services/sigNoz.schemas.ts @@ -2175,6 +2175,7 @@ export enum CoretypesKindDTO { 'factor-api-key' = 'factor-api-key', license = 'license', subscription = 'subscription', + 'deployment-host' = 'deployment-host', logs = 'logs', traces = 'traces', metrics = 'metrics', diff --git a/frontend/src/components/QueryBuilderV2/QueryBuilderV2.tsx b/frontend/src/components/QueryBuilderV2/QueryBuilderV2.tsx index 78a2071e38c..2734a858563 100644 --- a/frontend/src/components/QueryBuilderV2/QueryBuilderV2.tsx +++ b/frontend/src/components/QueryBuilderV2/QueryBuilderV2.tsx @@ -241,28 +241,29 @@ export const QueryBuilderV2 = memo(function QueryBuilderV2({ )) )} - {!showOnlyWhereClause && currentQuery.builder.queryFormulas.length > 0 && ( -
- {currentQuery.builder.queryFormulas.map((formula, index) => { - const query = - currentQuery.builder.queryData[index] || - currentQuery.builder.queryData[0]; - - return ( -
- -
- ); - })} -
- )} + {!showOnlyWhereClause && + currentQuery.builder.queryFormulas?.length > 0 && ( +
+ {currentQuery.builder.queryFormulas.map((formula, index) => { + const query = + currentQuery.builder.queryData[index] || + currentQuery.builder.queryData[0]; + + return ( +
+ +
+ ); + })} +
+ )} {shouldShowFooter && ( ))} - {currentQuery.builder.queryFormulas.map((formula) => ( + {currentQuery.builder.queryFormulas?.map((formula) => (
{formula.queryName}
diff --git a/frontend/src/components/QueryBuilderV2/QueryV2/__tests__/QueryV2.test.tsx b/frontend/src/components/QueryBuilderV2/QueryV2/__tests__/QueryV2.test.tsx index 09f95b27ccb..d5c39e5041e 100644 --- a/frontend/src/components/QueryBuilderV2/QueryV2/__tests__/QueryV2.test.tsx +++ b/frontend/src/components/QueryBuilderV2/QueryV2/__tests__/QueryV2.test.tsx @@ -212,6 +212,32 @@ describe('QueryBuilderV2 + QueryV2 - base render', () => { expect(handleRunQueryMock).toHaveBeenCalled(); }); + it('does not crash when builder.queryFormulas/queryTraceOperator are missing (partial/legacy query)', () => { + const currentQueryBase = baseQBContext.currentQuery as Query; + + mockedUseQueryBuilder.mockReturnValue({ + ...baseQBContext, + currentQuery: { + ...currentQueryBase, + builder: { + queryData: currentQueryBase.builder.queryData, + queryFormulas: undefined as unknown as [], + queryTraceOperator: undefined as unknown as [], + }, + }, + }); + + expect(() => + render(), + ).not.toThrow(); + + // query list still renders from queryData, formulas block is skipped + expect(document.querySelector('.query-names-section')).toBeInTheDocument(); + expect( + document.querySelector('.qb-formulas-container'), + ).not.toBeInTheDocument(); + }); + it('fx button is disabled when functions already exist', () => { const currentQueryBase = baseQBContext.currentQuery as Query; const supersetQueryBase = baseQBContext.supersetQuery as Query; diff --git a/frontend/src/components/ServiceAccountDrawer/__tests__/ServiceAccountDrawer.authz.test.tsx b/frontend/src/components/ServiceAccountDrawer/__tests__/ServiceAccountDrawer.authz.test.tsx index 8ace6d1c10d..f8fef2dc1a1 100644 --- a/frontend/src/components/ServiceAccountDrawer/__tests__/ServiceAccountDrawer.authz.test.tsx +++ b/frontend/src/components/ServiceAccountDrawer/__tests__/ServiceAccountDrawer.authz.test.tsx @@ -4,7 +4,7 @@ import { } from 'mocks-server/__mockdata__/roles'; import { rest, server } from 'mocks-server/server'; import { NuqsTestingAdapter } from 'nuqs/adapters/testing'; -import { fireEvent, render, screen, waitFor } from 'tests/test-utils'; +import { render, screen, waitFor } from 'tests/test-utils'; import { setupAuthzAdmin, setupAuthzDeny, @@ -110,10 +110,7 @@ describe('ServiceAccountDrawer — permissions', () => { it('shows PermissionDeniedCallout in Keys tab when list-keys permission is denied', async () => { server.use(setupAuthzDeny(APIKeyListPermission)); - renderDrawer(); - await screen.findByDisplayValue('CI Bot'); - - fireEvent.click(screen.getByRole('radio', { name: /keys/i })); + renderDrawer({ account: 'sa-1', tab: 'keys' }); await waitFor(() => { expect(screen.getByText(/list:factor-api-key/)).toBeInTheDocument(); diff --git a/frontend/src/container/FormAlertRules/utils.ts b/frontend/src/container/FormAlertRules/utils.ts index 967b00a921b..6deb1ad0f10 100644 --- a/frontend/src/container/FormAlertRules/utils.ts +++ b/frontend/src/container/FormAlertRules/utils.ts @@ -53,17 +53,24 @@ export const getUpdatedStepInterval = (evalWindow?: string): number => { }; export const getSelectedQueryOptions = ( - queries: Array< - | IBuilderQuery - | IBuilderTraceOperator - | IBuilderFormula - | IClickHouseQuery - | IPromQLQuery - >, -): SelectProps['options'] => - queries + queries: + | Array< + | IBuilderQuery + | IBuilderTraceOperator + | IBuilderFormula + | IClickHouseQuery + | IPromQLQuery + > + | undefined + | null, +): SelectProps['options'] => { + if (!queries) { + return []; + } + return queries .filter((query) => !query.disabled) .map((query) => ({ label: 'queryName' in query ? query.queryName : query.name, value: 'queryName' in query ? query.queryName : query.name, })); +}; diff --git a/frontend/src/hooks/queryBuilder/useGetCompositeQueryParam.ts b/frontend/src/hooks/queryBuilder/useGetCompositeQueryParam.ts index 0989c2e4237..6d27779828c 100644 --- a/frontend/src/hooks/queryBuilder/useGetCompositeQueryParam.ts +++ b/frontend/src/hooks/queryBuilder/useGetCompositeQueryParam.ts @@ -27,6 +27,14 @@ export const useGetCompositeQueryParam = (): Query | null => { decodeURIComponent(compositeQuery.replace(/\+/g, ' ')), ); + // Add default values for optional fields if empty + if (parsedCompositeQuery?.builder) { + parsedCompositeQuery.builder.queryFormulas = + parsedCompositeQuery.builder.queryFormulas ?? []; + parsedCompositeQuery.builder.queryTraceOperator = + parsedCompositeQuery.builder.queryTraceOperator ?? []; + } + // Convert old format to new format for each query in builder.queryData if (parsedCompositeQuery?.builder?.queryData) { parsedCompositeQuery.builder.queryData = diff --git a/frontend/src/providers/QueryBuilder.tsx b/frontend/src/providers/QueryBuilder.tsx index 4ca154173cf..fbadfdaa2c3 100644 --- a/frontend/src/providers/QueryBuilder.tsx +++ b/frontend/src/providers/QueryBuilder.tsx @@ -163,20 +163,23 @@ export function QueryBuilderProvider({ const prepareQueryBuilderData = useCallback( (query: Query): Query => { const builder: QueryBuilderData = { - queryData: query.builder.queryData?.map((item) => ({ - ...initialQueryBuilderFormValuesMap[ - initialDataSource || DataSource.METRICS - ], - ...item, - })), - queryFormulas: query.builder.queryFormulas?.map((item) => ({ - ...initialFormulaBuilderFormValues, - ...item, - })), - queryTraceOperator: query.builder.queryTraceOperator?.map((item) => ({ - ...initialQueryBuilderFormTraceOperatorValues, - ...item, - })), + queryData: + query.builder.queryData?.map((item) => ({ + ...initialQueryBuilderFormValuesMap[ + initialDataSource || DataSource.METRICS + ], + ...item, + })) ?? [], + queryFormulas: + query.builder.queryFormulas?.map((item) => ({ + ...initialFormulaBuilderFormValues, + ...item, + })) ?? [], + queryTraceOperator: + query.builder.queryTraceOperator?.map((item) => ({ + ...initialQueryBuilderFormTraceOperatorValues, + ...item, + })) ?? [], }; const setupedQueryData = builder.queryData.map((item) => { @@ -209,15 +212,17 @@ export function QueryBuilderProvider({ return currentElement; }); - const promql: IPromQLQuery[] = query.promql.map((item) => ({ - ...initialQueryPromQLData, - ...item, - })); + const promql: IPromQLQuery[] = + query.promql?.map((item) => ({ + ...initialQueryPromQLData, + ...item, + })) ?? []; - const clickHouse: IClickHouseQuery[] = query.clickhouse_sql.map((item) => ({ - ...initialClickHouseData, - ...item, - })); + const clickHouse: IClickHouseQuery[] = + query.clickhouse_sql?.map((item) => ({ + ...initialClickHouseData, + ...item, + })) ?? []; const newQueryState: QueryState = { clickhouse_sql: clickHouse, diff --git a/frontend/src/schemas/generated/transactionGroups.schema.json b/frontend/src/schemas/generated/transactionGroups.schema.json index bb7bd0fca65..7b2333aa5c2 100644 --- a/frontend/src/schemas/generated/transactionGroups.schema.json +++ b/frontend/src/schemas/generated/transactionGroups.schema.json @@ -66,6 +66,7 @@ "factor-api-key", "license", "subscription", + "deployment-host", "logs", "traces", "metrics", diff --git a/pkg/apiserver/signozapiserver/zeus.go b/pkg/apiserver/signozapiserver/zeus.go index 93299825f3d..3a5e4756937 100644 --- a/pkg/apiserver/signozapiserver/zeus.go +++ b/pkg/apiserver/signozapiserver/zeus.go @@ -5,6 +5,8 @@ import ( "github.com/SigNoz/signoz/pkg/http/handler" "github.com/SigNoz/signoz/pkg/types" + "github.com/SigNoz/signoz/pkg/types/authtypes" + "github.com/SigNoz/signoz/pkg/types/coretypes" "github.com/SigNoz/signoz/pkg/types/zeustypes" "github.com/gorilla/mux" ) @@ -27,7 +29,7 @@ func (provider *provider) addZeusRoutes(router *mux.Router) error { return err } - if err := router.Handle("/api/v2/zeus/hosts", handler.New(provider.authzMiddleware.ViewAccess(provider.zeusHandler.GetHosts), handler.OpenAPIDef{ + if err := router.Handle("/api/v2/zeus/hosts", handler.New(provider.authzMiddleware.CheckResources(provider.zeusHandler.GetHosts, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName, authtypes.SigNozViewerRoleName), handler.OpenAPIDef{ ID: "GetHosts", Tags: []string{"zeus"}, Summary: "Get host info from Zeus.", @@ -39,12 +41,17 @@ func (provider *provider) addZeusRoutes(router *mux.Router) error { SuccessStatusCode: http.StatusOK, ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound}, Deprecated: false, - SecuritySchemes: newSecuritySchemes(types.RoleViewer), - })).Methods(http.MethodGet).GetError(); err != nil { + SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceDeploymentHost.Scope(coretypes.VerbList)}), + }, handler.WithResourceDefs(handler.BasicResourceDef{ + Resource: coretypes.ResourceMetaResourceDeploymentHost, + Verb: coretypes.VerbList, + Category: coretypes.ActionCategoryDataAccess, + Selector: coretypes.WildcardSelector, + }))).Methods(http.MethodGet).GetError(); err != nil { return err } - if err := router.Handle("/api/v2/zeus/hosts", handler.New(provider.authzMiddleware.AdminAccess(provider.zeusHandler.PutHost), handler.OpenAPIDef{ + if err := router.Handle("/api/v2/zeus/hosts", handler.New(provider.authzMiddleware.CheckResources(provider.zeusHandler.PutHost, authtypes.SigNozAdminRoleName), handler.OpenAPIDef{ ID: "PutHost", Tags: []string{"zeus"}, Summary: "Put host in Zeus for a deployment.", @@ -56,8 +63,14 @@ func (provider *provider) addZeusRoutes(router *mux.Router) error { SuccessStatusCode: http.StatusNoContent, ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound, http.StatusConflict}, Deprecated: false, - SecuritySchemes: newSecuritySchemes(types.RoleAdmin), - })).Methods(http.MethodPut).GetError(); err != nil { + SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceDeploymentHost.Scope(coretypes.VerbUpdate)}), + }, handler.WithResourceDefs(handler.BasicResourceDef{ + Resource: coretypes.ResourceMetaResourceDeploymentHost, + Verb: coretypes.VerbUpdate, + Category: coretypes.ActionCategoryConfigurationChange, + ID: coretypes.BodyJSONPath("name"), + Selector: coretypes.WildcardSelector, + }))).Methods(http.MethodPut).GetError(); err != nil { return err } diff --git a/pkg/signoz/provider.go b/pkg/signoz/provider.go index 52d80b2e194..68cc7ab4a2d 100644 --- a/pkg/signoz/provider.go +++ b/pkg/signoz/provider.go @@ -244,6 +244,7 @@ func NewSQLMigrationProviderFactories( sqlmigration.NewDeleteOrphanUserRolesFactory(), sqlmigration.NewMigrateLambdaDashboardsFactory(), sqlmigration.NewAddAuthDomainTuplesFactory(sqlstore), + sqlmigration.NewAddDeploymentHostTuplesFactory(sqlstore), ) } diff --git a/pkg/sqlmigration/118_add_deployment_host_tuples.go b/pkg/sqlmigration/118_add_deployment_host_tuples.go new file mode 100644 index 00000000000..026fff64851 --- /dev/null +++ b/pkg/sqlmigration/118_add_deployment_host_tuples.go @@ -0,0 +1,161 @@ +package sqlmigration + +import ( + "context" + "database/sql" + "encoding/json" + "time" + + "github.com/SigNoz/signoz/pkg/factory" + "github.com/SigNoz/signoz/pkg/sqlstore" + "github.com/SigNoz/signoz/pkg/types/authtypes" + "github.com/SigNoz/signoz/pkg/types/coretypes" + "github.com/oklog/ulid/v2" + "github.com/uptrace/bun" + "github.com/uptrace/bun/dialect" + "github.com/uptrace/bun/migrate" +) + +type addDeploymentHostTuples struct { + sqlstore sqlstore.SQLStore +} + +func NewAddDeploymentHostTuplesFactory(sqlstore sqlstore.SQLStore) factory.ProviderFactory[SQLMigration, Config] { + return factory.NewProviderFactory(factory.MustNewName("add_deployment_host_tuples"), func(ctx context.Context, ps factory.ProviderSettings, c Config) (SQLMigration, error) { + return &addDeploymentHostTuples{sqlstore: sqlstore}, nil + }) +} + +func (migration *addDeploymentHostTuples) Register(migrations *migrate.Migrations) error { + return migrations.Register(migration.Up, migration.Down) +} + +func (migration *addDeploymentHostTuples) Up(ctx context.Context, db *bun.DB) error { + tx, err := db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + + var storeID string + err = tx.QueryRowContext(ctx, `SELECT id FROM store WHERE name = ? LIMIT 1`, "signoz").Scan(&storeID) + if err != nil { + return err + } + + var orgIDs []string + err = tx.NewSelect(). + Table("organizations"). + Column("id"). + Scan(ctx, &orgIDs) + if err != nil && err != sql.ErrNoRows { + return err + } + + isPG := migration.sqlstore.BunDB().Dialect().Name() == dialect.PG + + // zeus hosts moved from the legacy ViewAccess/AdminAccess role gates to + // CheckResources, which on enterprise requires real tuples -- existing orgs + // never had these written, only new orgs get them from the registry at bootstrap. + tuples := []migrationTuple{ + {authtypes.SigNozAdminRoleName, "metaresource", "deployment-host", "list"}, + {authtypes.SigNozAdminRoleName, "metaresource", "deployment-host", "update"}, + {authtypes.SigNozEditorRoleName, "metaresource", "deployment-host", "list"}, + {authtypes.SigNozViewerRoleName, "metaresource", "deployment-host", "list"}, + } + + for _, orgID := range orgIDs { + for _, tuple := range tuples { + entropy := ulid.DefaultEntropy() + now := time.Now().UTC() + tupleID := ulid.MustNew(ulid.Timestamp(now), entropy).String() + + objectID := "organization/" + orgID + "/" + tuple.objectName + "/*" + roleSubject := "organization/" + orgID + "/role/" + tuple.roleName + + if isPG { + user := "role:" + roleSubject + "#assignee" + result, err := tx.ExecContext(ctx, ` + INSERT INTO tuple (store, object_type, object_id, relation, _user, user_type, ulid, inserted_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT (store, object_type, object_id, relation, _user) DO NOTHING`, + storeID, tuple.objectType, objectID, tuple.relation, user, "userset", tupleID, now, + ) + if err != nil { + return err + } + rowsAffected, err := result.RowsAffected() + if err != nil { + return err + } + if rowsAffected == 0 { + continue + } + _, err = tx.ExecContext(ctx, ` + INSERT INTO changelog (store, object_type, object_id, relation, _user, operation, ulid, inserted_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT (store, ulid, object_type) DO NOTHING`, + storeID, tuple.objectType, objectID, tuple.relation, user, 0, tupleID, now, + ) + if err != nil { + return err + } + } else { + result, err := tx.ExecContext(ctx, ` + INSERT INTO tuple (store, object_type, object_id, relation, user_object_type, user_object_id, user_relation, user_type, ulid, inserted_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT (store, object_type, object_id, relation, user_object_type, user_object_id, user_relation) DO NOTHING`, + storeID, tuple.objectType, objectID, tuple.relation, "role", roleSubject, "assignee", "userset", tupleID, now, + ) + if err != nil { + return err + } + rowsAffected, err := result.RowsAffected() + if err != nil { + return err + } + if rowsAffected == 0 { + continue + } + _, err = tx.ExecContext(ctx, ` + INSERT INTO changelog (store, object_type, object_id, relation, user_object_type, user_object_id, user_relation, operation, ulid, inserted_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT (store, ulid, object_type) DO NOTHING`, + storeID, tuple.objectType, objectID, tuple.relation, "role", roleSubject, "assignee", 0, tupleID, now, + ) + if err != nil { + return err + } + } + } + } + + managedRoleGroups := make(map[string]string, len(coretypes.ManagedRoleToTransactions)) + for roleName, transactions := range coretypes.ManagedRoleToTransactions { + data, err := json.Marshal(authtypes.NewTransactionGroupsFromTransactions(transactions)) + if err != nil { + return err + } + managedRoleGroups[roleName] = string(data) + } + + for _, orgID := range orgIDs { + for roleName, data := range managedRoleGroups { + if _, err := tx.NewUpdate(). + Model(new(roles)). + Set("transaction_groups = ?", data). + Where("org_id = ?", orgID). + Where("type = ?", authtypes.RoleTypeManaged.StringValue()). + Where("name = ?", roleName). + Exec(ctx); err != nil { + return err + } + } + } + + return tx.Commit() +} + +func (migration *addDeploymentHostTuples) Down(context.Context, *bun.DB) error { + return nil +} diff --git a/pkg/types/coretypes/kind.go b/pkg/types/coretypes/kind.go index 41cfb6f7b6a..7cca0a874ae 100644 --- a/pkg/types/coretypes/kind.go +++ b/pkg/types/coretypes/kind.go @@ -71,6 +71,7 @@ func (name Kind) Enum() []any { KindFactorAPIKey, KindLicense, KindSubscription, + KindDeploymentHost, KindLogs, KindTraces, KindMetrics, diff --git a/pkg/types/coretypes/registry_kind.go b/pkg/types/coretypes/registry_kind.go index d4ab9e176bd..e9de2c1e810 100644 --- a/pkg/types/coretypes/registry_kind.go +++ b/pkg/types/coretypes/registry_kind.go @@ -31,6 +31,7 @@ var Kinds = []Kind{ KindFactorAPIKey, KindLicense, KindSubscription, + KindDeploymentHost, KindLogs, KindTraces, KindMetrics, @@ -71,6 +72,7 @@ var ( KindFactorAPIKey = MustNewKind("factor-api-key") KindLicense = MustNewKind("license") KindSubscription = MustNewKind("subscription") + KindDeploymentHost = MustNewKind("deployment-host") KindLogs = MustNewKind("logs") KindTraces = MustNewKind("traces") KindMetrics = MustNewKind("metrics") diff --git a/pkg/types/coretypes/registry_managed_role.go b/pkg/types/coretypes/registry_managed_role.go index 3cb0e6d823a..9e23defb6f7 100644 --- a/pkg/types/coretypes/registry_managed_role.go +++ b/pkg/types/coretypes/registry_managed_role.go @@ -191,6 +191,9 @@ var ManagedRoleToTransactions = map[string][]Transaction{ {Verb: VerbRead, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindTTLSetting}, WildCardSelectorString)}, {Verb: VerbUpdate, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindTTLSetting}, WildCardSelectorString)}, {Verb: VerbList, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindTTLSetting}, WildCardSelectorString)}, + // deployment-host — admin updates, viewer lists + {Verb: VerbList, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindDeploymentHost}, WildCardSelectorString)}, + {Verb: VerbUpdate, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindDeploymentHost}, WildCardSelectorString)}, // user-preference — every authenticated user can read+update their own {Verb: VerbRead, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindUserPreference}, WildCardSelectorString)}, {Verb: VerbUpdate, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindUserPreference}, WildCardSelectorString)}, @@ -283,6 +286,8 @@ var ManagedRoleToTransactions = map[string][]Transaction{ // ttl-setting — read only (admin updates) {Verb: VerbRead, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindTTLSetting}, WildCardSelectorString)}, {Verb: VerbList, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindTTLSetting}, WildCardSelectorString)}, + // deployment-host — list only (admin updates) + {Verb: VerbList, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindDeploymentHost}, WildCardSelectorString)}, // user-preference — every authenticated user can read+update their own {Verb: VerbRead, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindUserPreference}, WildCardSelectorString)}, {Verb: VerbUpdate, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindUserPreference}, WildCardSelectorString)}, @@ -341,6 +346,8 @@ var ManagedRoleToTransactions = map[string][]Transaction{ // ttl-setting — read only {Verb: VerbRead, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindTTLSetting}, WildCardSelectorString)}, {Verb: VerbList, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindTTLSetting}, WildCardSelectorString)}, + // deployment-host — list only + {Verb: VerbList, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindDeploymentHost}, WildCardSelectorString)}, // user-preference — every authenticated user can read+update their own {Verb: VerbRead, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindUserPreference}, WildCardSelectorString)}, {Verb: VerbUpdate, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindUserPreference}, WildCardSelectorString)}, diff --git a/pkg/types/coretypes/registry_resource.go b/pkg/types/coretypes/registry_resource.go index 030494cd895..fc09f125ee9 100644 --- a/pkg/types/coretypes/registry_resource.go +++ b/pkg/types/coretypes/registry_resource.go @@ -31,6 +31,7 @@ var Resources = []Resource{ ResourceMetaResourceFactorAPIKey, ResourceMetaResourceLicense, ResourceMetaResourceSubscription, + ResourceMetaResourceDeploymentHost, ResourceTelemetryResourceLogs, ResourceTelemetryResourceTraces, ResourceTelemetryResourceMetrics, @@ -71,6 +72,7 @@ var ( ResourceMetaResourceFactorAPIKey = NewResourceMetaResource(KindFactorAPIKey, VerbCreate, VerbList, VerbRead, VerbUpdate, VerbDelete) ResourceMetaResourceLicense = NewResourceMetaResource(KindLicense) ResourceMetaResourceSubscription = NewResourceMetaResource(KindSubscription) + ResourceMetaResourceDeploymentHost = NewResourceMetaResource(KindDeploymentHost, VerbList, VerbUpdate) ResourceTelemetryResourceLogs = NewResourceTelemetryResource(KindLogs) ResourceTelemetryResourceTraces = NewResourceTelemetryResource(KindTraces) ResourceTelemetryResourceMetrics = NewResourceTelemetryResource(KindMetrics) diff --git a/tests/integration/testdata/role/managed_role_transactions.json b/tests/integration/testdata/role/managed_role_transactions.json index ce196598273..f94b56f2813 100644 --- a/tests/integration/testdata/role/managed_role_transactions.json +++ b/tests/integration/testdata/role/managed_role_transactions.json @@ -295,6 +295,14 @@ "list" ] }, + { + "type": "metaresource", + "kind": "deployment-host", + "verbs": [ + "list", + "update" + ] + }, { "type": "metaresource", "kind": "user-preference", @@ -498,6 +506,13 @@ "list" ] }, + { + "type": "metaresource", + "kind": "deployment-host", + "verbs": [ + "list" + ] + }, { "type": "metaresource", "kind": "user-preference", @@ -654,6 +669,13 @@ "list" ] }, + { + "type": "metaresource", + "kind": "deployment-host", + "verbs": [ + "list" + ] + }, { "type": "metaresource", "kind": "user-preference",