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",