mirror of
https://github.com/SigNoz/signoz.git
synced 2026-03-17 18:32:11 +00:00
Compare commits
6 Commits
feat/markd
...
feat/fix-u
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdf961311f | ||
|
|
debe1608d0 | ||
|
|
e7dc4dfb2a | ||
|
|
a9f13a5bfd | ||
|
|
2cfd894d4c | ||
|
|
6e772fb58d |
4
.github/CODEOWNERS
vendored
4
.github/CODEOWNERS
vendored
@@ -105,10 +105,6 @@ go.mod @therealpandey
|
||||
/pkg/modules/authdomain/ @vikrantgupta25
|
||||
/pkg/modules/role/ @vikrantgupta25
|
||||
|
||||
# IdentN Owners
|
||||
/pkg/identn/ @vikrantgupta25
|
||||
/pkg/http/middleware/identn.go @vikrantgupta25
|
||||
|
||||
# Integration tests
|
||||
|
||||
/tests/integration/ @vikrantgupta25
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
"github.com/SigNoz/signoz/pkg/querier"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app"
|
||||
"github.com/SigNoz/signoz/pkg/queryparser"
|
||||
@@ -73,8 +74,8 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
},
|
||||
signoz.NewSQLStoreProviderFactories(),
|
||||
signoz.NewTelemetryStoreProviderFactories(),
|
||||
func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing) (map[authtypes.AuthNProvider]authn.AuthN, error) {
|
||||
return signoz.NewAuthNs(ctx, providerSettings, store, licensing)
|
||||
func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing, userGetter user.Getter) (map[authtypes.AuthNProvider]authn.AuthN, error) {
|
||||
return signoz.NewAuthNs(ctx, providerSettings, store, licensing, userGetter)
|
||||
},
|
||||
func(ctx context.Context, sqlstore sqlstore.SQLStore, _ licensing.Licensing, _ dashboard.Module) factory.ProviderFactory[authz.AuthZ, authz.Config] {
|
||||
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx))
|
||||
|
||||
@@ -9,12 +9,12 @@ import (
|
||||
"github.com/SigNoz/signoz/ee/authn/callbackauthn/oidccallbackauthn"
|
||||
"github.com/SigNoz/signoz/ee/authn/callbackauthn/samlcallbackauthn"
|
||||
"github.com/SigNoz/signoz/ee/authz/openfgaauthz"
|
||||
eequerier "github.com/SigNoz/signoz/ee/querier"
|
||||
"github.com/SigNoz/signoz/ee/authz/openfgaschema"
|
||||
"github.com/SigNoz/signoz/ee/gateway/httpgateway"
|
||||
enterpriselicensing "github.com/SigNoz/signoz/ee/licensing"
|
||||
"github.com/SigNoz/signoz/ee/licensing/httplicensing"
|
||||
"github.com/SigNoz/signoz/ee/modules/dashboard/impldashboard"
|
||||
eequerier "github.com/SigNoz/signoz/ee/querier"
|
||||
enterpriseapp "github.com/SigNoz/signoz/ee/query-service/app"
|
||||
"github.com/SigNoz/signoz/ee/sqlschema/postgressqlschema"
|
||||
"github.com/SigNoz/signoz/ee/sqlstore/postgressqlstore"
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
pkgimpldashboard "github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
"github.com/SigNoz/signoz/pkg/querier"
|
||||
"github.com/SigNoz/signoz/pkg/queryparser"
|
||||
"github.com/SigNoz/signoz/pkg/signoz"
|
||||
@@ -95,7 +96,7 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
},
|
||||
sqlstoreFactories,
|
||||
signoz.NewTelemetryStoreProviderFactories(),
|
||||
func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing) (map[authtypes.AuthNProvider]authn.AuthN, error) {
|
||||
func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing, userGetter user.Getter) (map[authtypes.AuthNProvider]authn.AuthN, error) {
|
||||
samlCallbackAuthN, err := samlcallbackauthn.New(ctx, store, licensing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -106,7 +107,7 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authNs, err := signoz.NewAuthNs(ctx, providerSettings, store, licensing)
|
||||
authNs, err := signoz.NewAuthNs(ctx, providerSettings, store, licensing, userGetter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -321,19 +321,3 @@ user:
|
||||
org:
|
||||
name: default
|
||||
id: 00000000-0000-0000-0000-000000000000
|
||||
|
||||
##################### IdentN #####################
|
||||
identn:
|
||||
tokenizer:
|
||||
# toggle the identN resolver
|
||||
enabled: true
|
||||
# headers to use for tokenizer identN resolver
|
||||
headers:
|
||||
- Authorization
|
||||
- Sec-WebSocket-Protocol
|
||||
apikey:
|
||||
# toggle the identN resolver
|
||||
enabled: true
|
||||
# headers to use for apikey identN resolver
|
||||
headers:
|
||||
- SIGNOZ-API-KEY
|
||||
|
||||
@@ -220,6 +220,13 @@ components:
|
||||
- additions
|
||||
- deletions
|
||||
type: object
|
||||
AuthtypesPatchableRole:
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
required:
|
||||
- description
|
||||
type: object
|
||||
AuthtypesPostableAuthDomain:
|
||||
properties:
|
||||
config:
|
||||
@@ -236,6 +243,15 @@ components:
|
||||
password:
|
||||
type: string
|
||||
type: object
|
||||
AuthtypesPostableRole:
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
AuthtypesPostableRotateToken:
|
||||
properties:
|
||||
refreshToken:
|
||||
@@ -251,6 +267,31 @@ components:
|
||||
- name
|
||||
- type
|
||||
type: object
|
||||
AuthtypesRole:
|
||||
properties:
|
||||
createdAt:
|
||||
format: date-time
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
orgId:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
updatedAt:
|
||||
format: date-time
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- description
|
||||
- type
|
||||
- orgId
|
||||
type: object
|
||||
AuthtypesRoleMapping:
|
||||
properties:
|
||||
defaultRole:
|
||||
@@ -1722,47 +1763,6 @@ components:
|
||||
- status
|
||||
- error
|
||||
type: object
|
||||
RoletypesPatchableRole:
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
required:
|
||||
- description
|
||||
type: object
|
||||
RoletypesPostableRole:
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
RoletypesRole:
|
||||
properties:
|
||||
createdAt:
|
||||
format: date-time
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
orgId:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
updatedAt:
|
||||
format: date-time
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- description
|
||||
- type
|
||||
- orgId
|
||||
type: object
|
||||
ServiceaccounttypesFactorAPIKey:
|
||||
properties:
|
||||
createdAt:
|
||||
@@ -1984,52 +1984,6 @@ components:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
TypesChangePasswordRequest:
|
||||
properties:
|
||||
newPassword:
|
||||
type: string
|
||||
oldPassword:
|
||||
type: string
|
||||
userId:
|
||||
type: string
|
||||
type: object
|
||||
TypesGettableAPIKey:
|
||||
properties:
|
||||
createdAt:
|
||||
format: date-time
|
||||
type: string
|
||||
createdBy:
|
||||
type: string
|
||||
createdByUser:
|
||||
$ref: '#/components/schemas/TypesUser'
|
||||
expiresAt:
|
||||
format: int64
|
||||
type: integer
|
||||
id:
|
||||
type: string
|
||||
lastUsed:
|
||||
format: int64
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
revoked:
|
||||
type: boolean
|
||||
role:
|
||||
type: string
|
||||
token:
|
||||
type: string
|
||||
updatedAt:
|
||||
format: date-time
|
||||
type: string
|
||||
updatedBy:
|
||||
type: string
|
||||
updatedByUser:
|
||||
$ref: '#/components/schemas/TypesUser'
|
||||
userId:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
TypesGettableGlobalConfig:
|
||||
properties:
|
||||
external_url:
|
||||
@@ -2044,31 +1998,6 @@ components:
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
TypesInvite:
|
||||
properties:
|
||||
createdAt:
|
||||
format: date-time
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
inviteLink:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
orgId:
|
||||
type: string
|
||||
role:
|
||||
type: string
|
||||
token:
|
||||
type: string
|
||||
updatedAt:
|
||||
format: date-time
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
TypesOrganization:
|
||||
properties:
|
||||
alias:
|
||||
@@ -2091,7 +2020,78 @@ components:
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
TypesPostableAPIKey:
|
||||
UsertypesChangePasswordRequest:
|
||||
properties:
|
||||
newPassword:
|
||||
type: string
|
||||
oldPassword:
|
||||
type: string
|
||||
userId:
|
||||
type: string
|
||||
type: object
|
||||
UsertypesGettableAPIKey:
|
||||
properties:
|
||||
createdAt:
|
||||
format: date-time
|
||||
type: string
|
||||
createdBy:
|
||||
type: string
|
||||
createdByUser:
|
||||
$ref: '#/components/schemas/UsertypesUser'
|
||||
expiresAt:
|
||||
format: int64
|
||||
type: integer
|
||||
id:
|
||||
type: string
|
||||
lastUsed:
|
||||
format: int64
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
revoked:
|
||||
type: boolean
|
||||
role:
|
||||
type: string
|
||||
token:
|
||||
type: string
|
||||
updatedAt:
|
||||
format: date-time
|
||||
type: string
|
||||
updatedBy:
|
||||
type: string
|
||||
updatedByUser:
|
||||
$ref: '#/components/schemas/UsertypesUser'
|
||||
userId:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
UsertypesInvite:
|
||||
properties:
|
||||
createdAt:
|
||||
format: date-time
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
inviteLink:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
orgId:
|
||||
type: string
|
||||
role:
|
||||
type: string
|
||||
token:
|
||||
type: string
|
||||
updatedAt:
|
||||
format: date-time
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
UsertypesPostableAPIKey:
|
||||
properties:
|
||||
expiresInDays:
|
||||
format: int64
|
||||
@@ -2101,7 +2101,7 @@ components:
|
||||
role:
|
||||
type: string
|
||||
type: object
|
||||
TypesPostableAcceptInvite:
|
||||
UsertypesPostableAcceptInvite:
|
||||
properties:
|
||||
displayName:
|
||||
type: string
|
||||
@@ -2112,16 +2112,16 @@ components:
|
||||
token:
|
||||
type: string
|
||||
type: object
|
||||
TypesPostableBulkInviteRequest:
|
||||
UsertypesPostableBulkInviteRequest:
|
||||
properties:
|
||||
invites:
|
||||
items:
|
||||
$ref: '#/components/schemas/TypesPostableInvite'
|
||||
$ref: '#/components/schemas/UsertypesPostableInvite'
|
||||
type: array
|
||||
required:
|
||||
- invites
|
||||
type: object
|
||||
TypesPostableForgotPassword:
|
||||
UsertypesPostableForgotPassword:
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
@@ -2133,7 +2133,7 @@ components:
|
||||
- orgId
|
||||
- email
|
||||
type: object
|
||||
TypesPostableInvite:
|
||||
UsertypesPostableInvite:
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
@@ -2144,14 +2144,14 @@ components:
|
||||
role:
|
||||
type: string
|
||||
type: object
|
||||
TypesPostableResetPassword:
|
||||
UsertypesPostableResetPassword:
|
||||
properties:
|
||||
password:
|
||||
type: string
|
||||
token:
|
||||
type: string
|
||||
type: object
|
||||
TypesResetPasswordToken:
|
||||
UsertypesResetPasswordToken:
|
||||
properties:
|
||||
expiresAt:
|
||||
format: date-time
|
||||
@@ -2165,7 +2165,7 @@ components:
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
TypesStorableAPIKey:
|
||||
UsertypesStorableAPIKey:
|
||||
properties:
|
||||
createdAt:
|
||||
format: date-time
|
||||
@@ -2192,7 +2192,7 @@ components:
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
TypesUser:
|
||||
UsertypesUser:
|
||||
properties:
|
||||
createdAt:
|
||||
format: date-time
|
||||
@@ -2392,7 +2392,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TypesChangePasswordRequest'
|
||||
$ref: '#/components/schemas/UsertypesChangePasswordRequest'
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
@@ -3197,7 +3197,7 @@ paths:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/TypesResetPasswordToken'
|
||||
$ref: '#/components/schemas/UsertypesResetPasswordToken'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
@@ -3302,7 +3302,7 @@ paths:
|
||||
properties:
|
||||
data:
|
||||
items:
|
||||
$ref: '#/components/schemas/TypesInvite'
|
||||
$ref: '#/components/schemas/UsertypesInvite'
|
||||
type: array
|
||||
status:
|
||||
type: string
|
||||
@@ -3345,7 +3345,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TypesPostableInvite'
|
||||
$ref: '#/components/schemas/UsertypesPostableInvite'
|
||||
responses:
|
||||
"201":
|
||||
content:
|
||||
@@ -3353,7 +3353,7 @@ paths:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/TypesInvite'
|
||||
$ref: '#/components/schemas/UsertypesInvite'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
@@ -3469,7 +3469,7 @@ paths:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/TypesInvite'
|
||||
$ref: '#/components/schemas/UsertypesInvite'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
@@ -3507,7 +3507,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TypesPostableAcceptInvite'
|
||||
$ref: '#/components/schemas/UsertypesPostableAcceptInvite'
|
||||
responses:
|
||||
"201":
|
||||
content:
|
||||
@@ -3515,7 +3515,7 @@ paths:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/TypesUser'
|
||||
$ref: '#/components/schemas/UsertypesUser'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
@@ -3553,7 +3553,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TypesPostableBulkInviteRequest'
|
||||
$ref: '#/components/schemas/UsertypesPostableBulkInviteRequest'
|
||||
responses:
|
||||
"201":
|
||||
description: Created
|
||||
@@ -3878,7 +3878,7 @@ paths:
|
||||
properties:
|
||||
data:
|
||||
items:
|
||||
$ref: '#/components/schemas/TypesGettableAPIKey'
|
||||
$ref: '#/components/schemas/UsertypesGettableAPIKey'
|
||||
type: array
|
||||
status:
|
||||
type: string
|
||||
@@ -3921,7 +3921,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TypesPostableAPIKey'
|
||||
$ref: '#/components/schemas/UsertypesPostableAPIKey'
|
||||
responses:
|
||||
"201":
|
||||
content:
|
||||
@@ -3929,7 +3929,7 @@ paths:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/TypesGettableAPIKey'
|
||||
$ref: '#/components/schemas/UsertypesGettableAPIKey'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
@@ -4035,7 +4035,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TypesStorableAPIKey'
|
||||
$ref: '#/components/schemas/UsertypesStorableAPIKey'
|
||||
responses:
|
||||
"204":
|
||||
content:
|
||||
@@ -4196,7 +4196,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TypesPostableResetPassword'
|
||||
$ref: '#/components/schemas/UsertypesPostableResetPassword'
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
@@ -4234,7 +4234,7 @@ paths:
|
||||
properties:
|
||||
data:
|
||||
items:
|
||||
$ref: '#/components/schemas/RoletypesRole'
|
||||
$ref: '#/components/schemas/AuthtypesRole'
|
||||
type: array
|
||||
status:
|
||||
type: string
|
||||
@@ -4277,7 +4277,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RoletypesPostableRole'
|
||||
$ref: '#/components/schemas/AuthtypesPostableRole'
|
||||
responses:
|
||||
"201":
|
||||
content:
|
||||
@@ -4422,7 +4422,7 @@ paths:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/RoletypesRole'
|
||||
$ref: '#/components/schemas/AuthtypesRole'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
@@ -4470,7 +4470,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RoletypesPatchableRole'
|
||||
$ref: '#/components/schemas/AuthtypesPatchableRole'
|
||||
responses:
|
||||
"204":
|
||||
content:
|
||||
@@ -5271,7 +5271,7 @@ paths:
|
||||
properties:
|
||||
data:
|
||||
items:
|
||||
$ref: '#/components/schemas/TypesUser'
|
||||
$ref: '#/components/schemas/UsertypesUser'
|
||||
type: array
|
||||
status:
|
||||
type: string
|
||||
@@ -5369,7 +5369,7 @@ paths:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/TypesUser'
|
||||
$ref: '#/components/schemas/UsertypesUser'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
@@ -5423,7 +5423,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TypesUser'
|
||||
$ref: '#/components/schemas/UsertypesUser'
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
@@ -5431,7 +5431,7 @@ paths:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/TypesUser'
|
||||
$ref: '#/components/schemas/UsertypesUser'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
@@ -5489,7 +5489,7 @@ paths:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/TypesUser'
|
||||
$ref: '#/components/schemas/UsertypesUser'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
@@ -5692,7 +5692,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TypesPostableForgotPassword'
|
||||
$ref: '#/components/schemas/UsertypesPostableForgotPassword'
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
openfgapkgtransformer "github.com/openfga/language/pkg/go/transformer"
|
||||
@@ -23,7 +22,7 @@ type provider struct {
|
||||
pkgAuthzService authz.AuthZ
|
||||
openfgaServer *openfgaserver.Server
|
||||
licensing licensing.Licensing
|
||||
store roletypes.Store
|
||||
store authtypes.RoleStore
|
||||
registry []authz.RegisterTypeable
|
||||
}
|
||||
|
||||
@@ -82,23 +81,23 @@ func (provider *provider) Write(ctx context.Context, additions []*openfgav1.Tupl
|
||||
return provider.openfgaServer.Write(ctx, additions, deletions)
|
||||
}
|
||||
|
||||
func (provider *provider) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*roletypes.Role, error) {
|
||||
func (provider *provider) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*authtypes.Role, error) {
|
||||
return provider.pkgAuthzService.Get(ctx, orgID, id)
|
||||
}
|
||||
|
||||
func (provider *provider) GetByOrgIDAndName(ctx context.Context, orgID valuer.UUID, name string) (*roletypes.Role, error) {
|
||||
func (provider *provider) GetByOrgIDAndName(ctx context.Context, orgID valuer.UUID, name string) (*authtypes.Role, error) {
|
||||
return provider.pkgAuthzService.GetByOrgIDAndName(ctx, orgID, name)
|
||||
}
|
||||
|
||||
func (provider *provider) List(ctx context.Context, orgID valuer.UUID) ([]*roletypes.Role, error) {
|
||||
func (provider *provider) List(ctx context.Context, orgID valuer.UUID) ([]*authtypes.Role, error) {
|
||||
return provider.pkgAuthzService.List(ctx, orgID)
|
||||
}
|
||||
|
||||
func (provider *provider) ListByOrgIDAndNames(ctx context.Context, orgID valuer.UUID, names []string) ([]*roletypes.Role, error) {
|
||||
func (provider *provider) ListByOrgIDAndNames(ctx context.Context, orgID valuer.UUID, names []string) ([]*authtypes.Role, error) {
|
||||
return provider.pkgAuthzService.ListByOrgIDAndNames(ctx, orgID, names)
|
||||
}
|
||||
|
||||
func (provider *provider) ListByOrgIDAndIDs(ctx context.Context, orgID valuer.UUID, ids []valuer.UUID) ([]*roletypes.Role, error) {
|
||||
func (provider *provider) ListByOrgIDAndIDs(ctx context.Context, orgID valuer.UUID, ids []valuer.UUID) ([]*authtypes.Role, error) {
|
||||
return provider.pkgAuthzService.ListByOrgIDAndIDs(ctx, orgID, ids)
|
||||
}
|
||||
|
||||
@@ -114,7 +113,7 @@ func (provider *provider) Revoke(ctx context.Context, orgID valuer.UUID, names [
|
||||
return provider.pkgAuthzService.Revoke(ctx, orgID, names, subject)
|
||||
}
|
||||
|
||||
func (provider *provider) CreateManagedRoles(ctx context.Context, orgID valuer.UUID, managedRoles []*roletypes.Role) error {
|
||||
func (provider *provider) CreateManagedRoles(ctx context.Context, orgID valuer.UUID, managedRoles []*authtypes.Role) error {
|
||||
return provider.pkgAuthzService.CreateManagedRoles(ctx, orgID, managedRoles)
|
||||
}
|
||||
|
||||
@@ -136,16 +135,16 @@ func (provider *provider) CreateManagedUserRoleTransactions(ctx context.Context,
|
||||
return provider.Write(ctx, tuples, nil)
|
||||
}
|
||||
|
||||
func (provider *provider) Create(ctx context.Context, orgID valuer.UUID, role *roletypes.Role) error {
|
||||
func (provider *provider) Create(ctx context.Context, orgID valuer.UUID, role *authtypes.Role) error {
|
||||
_, err := provider.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
return provider.store.Create(ctx, roletypes.NewStorableRoleFromRole(role))
|
||||
return provider.store.Create(ctx, authtypes.NewStorableRoleFromRole(role))
|
||||
}
|
||||
|
||||
func (provider *provider) GetOrCreate(ctx context.Context, orgID valuer.UUID, role *roletypes.Role) (*roletypes.Role, error) {
|
||||
func (provider *provider) GetOrCreate(ctx context.Context, orgID valuer.UUID, role *authtypes.Role) (*authtypes.Role, error) {
|
||||
_, err := provider.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
@@ -159,10 +158,10 @@ func (provider *provider) GetOrCreate(ctx context.Context, orgID valuer.UUID, ro
|
||||
}
|
||||
|
||||
if existingRole != nil {
|
||||
return roletypes.NewRoleFromStorableRole(existingRole), nil
|
||||
return authtypes.NewRoleFromStorableRole(existingRole), nil
|
||||
}
|
||||
|
||||
err = provider.store.Create(ctx, roletypes.NewStorableRoleFromRole(role))
|
||||
err = provider.store.Create(ctx, authtypes.NewStorableRoleFromRole(role))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -217,13 +216,13 @@ func (provider *provider) GetObjects(ctx context.Context, orgID valuer.UUID, id
|
||||
return objects, nil
|
||||
}
|
||||
|
||||
func (provider *provider) Patch(ctx context.Context, orgID valuer.UUID, role *roletypes.Role) error {
|
||||
func (provider *provider) Patch(ctx context.Context, orgID valuer.UUID, role *authtypes.Role) error {
|
||||
_, err := provider.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
return provider.store.Update(ctx, orgID, roletypes.NewStorableRoleFromRole(role))
|
||||
return provider.store.Update(ctx, orgID, authtypes.NewStorableRoleFromRole(role))
|
||||
}
|
||||
|
||||
func (provider *provider) PatchObjects(ctx context.Context, orgID valuer.UUID, name string, relation authtypes.Relation, additions, deletions []*authtypes.Object) error {
|
||||
@@ -232,12 +231,12 @@ func (provider *provider) PatchObjects(ctx context.Context, orgID valuer.UUID, n
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
additionTuples, err := roletypes.GetAdditionTuples(name, orgID, relation, additions)
|
||||
additionTuples, err := authtypes.GetAdditionTuples(name, orgID, relation, additions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
deletionTuples, err := roletypes.GetDeletionTuples(name, orgID, relation, deletions)
|
||||
deletionTuples, err := authtypes.GetDeletionTuples(name, orgID, relation, deletions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -261,7 +260,7 @@ func (provider *provider) Delete(ctx context.Context, orgID valuer.UUID, id valu
|
||||
return err
|
||||
}
|
||||
|
||||
role := roletypes.NewRoleFromStorableRole(storableRole)
|
||||
role := authtypes.NewRoleFromStorableRole(storableRole)
|
||||
err = role.ErrIfManaged()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -271,7 +270,7 @@ func (provider *provider) Delete(ctx context.Context, orgID valuer.UUID, id valu
|
||||
}
|
||||
|
||||
func (provider *provider) MustGetTypeables() []authtypes.Typeable {
|
||||
return []authtypes.Typeable{authtypes.TypeableRole, roletypes.TypeableResourcesRoles}
|
||||
return []authtypes.Typeable{authtypes.TypeableRole, authtypes.TypeableResourcesRoles}
|
||||
}
|
||||
|
||||
func (provider *provider) getManagedRoleGrantTuples(orgID valuer.UUID, userID valuer.UUID) ([]*openfgav1.TupleKey, error) {
|
||||
@@ -283,7 +282,7 @@ func (provider *provider) getManagedRoleGrantTuples(orgID valuer.UUID, userID va
|
||||
adminSubject,
|
||||
authtypes.RelationAssignee,
|
||||
[]authtypes.Selector{
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, roletypes.SigNozAdminRoleName),
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, authtypes.SigNozAdminRoleName),
|
||||
},
|
||||
orgID,
|
||||
)
|
||||
@@ -298,7 +297,7 @@ func (provider *provider) getManagedRoleGrantTuples(orgID valuer.UUID, userID va
|
||||
anonymousSubject,
|
||||
authtypes.RelationAssignee,
|
||||
[]authtypes.Selector{
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, roletypes.SigNozAnonymousRoleName),
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, authtypes.SigNozAnonymousRoleName),
|
||||
},
|
||||
orgID,
|
||||
)
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
@@ -214,7 +213,7 @@ func (module *module) Update(ctx context.Context, orgID valuer.UUID, id valuer.U
|
||||
return module.pkgDashboardModule.Update(ctx, orgID, id, updatedBy, data, diff)
|
||||
}
|
||||
|
||||
func (module *module) LockUnlock(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, role types.Role, lock bool) error {
|
||||
func (module *module) LockUnlock(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, role authtypes.LegacyRole, lock bool) error {
|
||||
return module.pkgDashboardModule.LockUnlock(ctx, orgID, id, updatedBy, role, lock)
|
||||
}
|
||||
|
||||
@@ -224,7 +223,7 @@ func (module *module) MustGetTypeables() []authtypes.Typeable {
|
||||
|
||||
func (module *module) MustGetManagedRoleTransactions() map[string][]*authtypes.Transaction {
|
||||
return map[string][]*authtypes.Transaction{
|
||||
roletypes.SigNozAnonymousRoleName: {
|
||||
authtypes.SigNozAnonymousRoleName: {
|
||||
{
|
||||
ID: valuer.GenerateUUID(),
|
||||
Relation: authtypes.RelationRead,
|
||||
|
||||
@@ -14,8 +14,8 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/usertypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/gorilla/mux"
|
||||
"log/slog"
|
||||
@@ -142,10 +142,10 @@ func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId
|
||||
"cloud_provider", cloudProvider,
|
||||
)
|
||||
|
||||
newPAT, err := types.NewStorableAPIKey(
|
||||
newPAT, err := usertypes.NewStorableAPIKey(
|
||||
integrationPATName,
|
||||
integrationUser.ID,
|
||||
types.RoleViewer,
|
||||
authtypes.RoleViewer,
|
||||
0,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -165,16 +165,16 @@ func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId
|
||||
|
||||
func (ah *APIHandler) getOrCreateCloudIntegrationUser(
|
||||
ctx context.Context, orgId string, cloudProvider string,
|
||||
) (*types.User, *basemodel.ApiError) {
|
||||
) (*usertypes.User, *basemodel.ApiError) {
|
||||
cloudIntegrationUserName := fmt.Sprintf("%s-integration", cloudProvider)
|
||||
email := valuer.MustNewEmail(fmt.Sprintf("%s@signoz.io", cloudIntegrationUserName))
|
||||
|
||||
cloudIntegrationUser, err := types.NewUser(cloudIntegrationUserName, email, types.RoleViewer, valuer.MustNewUUID(orgId), types.UserStatusActive)
|
||||
cloudIntegrationUser, err := usertypes.NewUser(cloudIntegrationUserName, email, authtypes.RoleViewer, valuer.MustNewUUID(orgId), usertypes.UserStatusActive)
|
||||
if err != nil {
|
||||
return nil, basemodel.InternalError(fmt.Errorf("couldn't create cloud integration user: %w", err))
|
||||
}
|
||||
|
||||
password := types.MustGenerateFactorPassword(cloudIntegrationUser.ID.StringValue())
|
||||
password := usertypes.MustGenerateFactorPassword(cloudIntegrationUser.ID.StringValue())
|
||||
|
||||
cloudIntegrationUser, err = ah.Signoz.Modules.User.GetOrCreateUser(ctx, cloudIntegrationUser, user.WithFactorPassword(password))
|
||||
if err != nil {
|
||||
|
||||
@@ -223,8 +223,7 @@ SELECT
|
||||
i.indisunique AS unique,
|
||||
i.indisprimary AS primary,
|
||||
a.attname AS column_name,
|
||||
array_position(i.indkey, a.attnum) AS column_position,
|
||||
pg_get_expr(i.indpred, i.indrelid) AS predicate
|
||||
array_position(i.indkey, a.attnum) AS column_position
|
||||
FROM
|
||||
pg_index i
|
||||
LEFT JOIN pg_class ct ON ct.oid = i.indrelid
|
||||
@@ -247,12 +246,7 @@ ORDER BY index_name, column_position`, string(name))
|
||||
}
|
||||
}()
|
||||
|
||||
type indexEntry struct {
|
||||
columns []sqlschema.ColumnName
|
||||
predicate *string
|
||||
}
|
||||
|
||||
uniqueIndicesMap := make(map[string]*indexEntry)
|
||||
uniqueIndicesMap := make(map[string]*sqlschema.UniqueIndex)
|
||||
for rows.Next() {
|
||||
var (
|
||||
tableName string
|
||||
@@ -262,50 +256,30 @@ ORDER BY index_name, column_position`, string(name))
|
||||
columnName string
|
||||
// starts from 0 and is unused in this function, this is to ensure that the column names are in the correct order
|
||||
columnPosition int
|
||||
predicate *string
|
||||
)
|
||||
|
||||
if err := rows.Scan(&tableName, &indexName, &unique, &primary, &columnName, &columnPosition, &predicate); err != nil {
|
||||
if err := rows.Scan(&tableName, &indexName, &unique, &primary, &columnName, &columnPosition); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if unique {
|
||||
if _, ok := uniqueIndicesMap[indexName]; !ok {
|
||||
uniqueIndicesMap[indexName] = &indexEntry{
|
||||
columns: []sqlschema.ColumnName{sqlschema.ColumnName(columnName)},
|
||||
predicate: predicate,
|
||||
uniqueIndicesMap[indexName] = &sqlschema.UniqueIndex{
|
||||
TableName: name,
|
||||
ColumnNames: []sqlschema.ColumnName{sqlschema.ColumnName(columnName)},
|
||||
}
|
||||
} else {
|
||||
uniqueIndicesMap[indexName].columns = append(uniqueIndicesMap[indexName].columns, sqlschema.ColumnName(columnName))
|
||||
uniqueIndicesMap[indexName].ColumnNames = append(uniqueIndicesMap[indexName].ColumnNames, sqlschema.ColumnName(columnName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
indices := make([]sqlschema.Index, 0)
|
||||
for indexName, entry := range uniqueIndicesMap {
|
||||
if entry.predicate != nil {
|
||||
index := &sqlschema.PartialUniqueIndex{
|
||||
TableName: name,
|
||||
ColumnNames: entry.columns,
|
||||
Where: *entry.predicate,
|
||||
}
|
||||
|
||||
if index.Name() == indexName {
|
||||
indices = append(indices, index)
|
||||
} else {
|
||||
indices = append(indices, index.Named(indexName))
|
||||
}
|
||||
for indexName, index := range uniqueIndicesMap {
|
||||
if index.Name() == indexName {
|
||||
indices = append(indices, index)
|
||||
} else {
|
||||
index := &sqlschema.UniqueIndex{
|
||||
TableName: name,
|
||||
ColumnNames: entry.columns,
|
||||
}
|
||||
|
||||
if index.Name() == indexName {
|
||||
indices = append(indices, index)
|
||||
} else {
|
||||
indices = append(indices, index.Named(indexName))
|
||||
}
|
||||
indices = append(indices, index.Named(indexName))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ import type { BodyType, ErrorType } from '../../../generatedAPIInstance';
|
||||
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
|
||||
import type {
|
||||
AuthtypesPatchableObjectsDTO,
|
||||
AuthtypesPatchableRoleDTO,
|
||||
AuthtypesPostableRoleDTO,
|
||||
CreateRole201,
|
||||
DeleteRolePathParameters,
|
||||
GetObjects200,
|
||||
@@ -31,8 +33,6 @@ import type {
|
||||
PatchObjectsPathParameters,
|
||||
PatchRolePathParameters,
|
||||
RenderErrorResponseDTO,
|
||||
RoletypesPatchableRoleDTO,
|
||||
RoletypesPostableRoleDTO,
|
||||
} from '../sigNoz.schemas';
|
||||
|
||||
/**
|
||||
@@ -118,14 +118,14 @@ export const invalidateListRoles = async (
|
||||
* @summary Create role
|
||||
*/
|
||||
export const createRole = (
|
||||
roletypesPostableRoleDTO: BodyType<RoletypesPostableRoleDTO>,
|
||||
authtypesPostableRoleDTO: BodyType<AuthtypesPostableRoleDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<CreateRole201>({
|
||||
url: `/api/v1/roles`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: roletypesPostableRoleDTO,
|
||||
data: authtypesPostableRoleDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
@@ -137,13 +137,13 @@ export const getCreateRoleMutationOptions = <
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createRole>>,
|
||||
TError,
|
||||
{ data: BodyType<RoletypesPostableRoleDTO> },
|
||||
{ data: BodyType<AuthtypesPostableRoleDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createRole>>,
|
||||
TError,
|
||||
{ data: BodyType<RoletypesPostableRoleDTO> },
|
||||
{ data: BodyType<AuthtypesPostableRoleDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['createRole'];
|
||||
@@ -157,7 +157,7 @@ export const getCreateRoleMutationOptions = <
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof createRole>>,
|
||||
{ data: BodyType<RoletypesPostableRoleDTO> }
|
||||
{ data: BodyType<AuthtypesPostableRoleDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
@@ -170,7 +170,7 @@ export const getCreateRoleMutationOptions = <
|
||||
export type CreateRoleMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof createRole>>
|
||||
>;
|
||||
export type CreateRoleMutationBody = BodyType<RoletypesPostableRoleDTO>;
|
||||
export type CreateRoleMutationBody = BodyType<AuthtypesPostableRoleDTO>;
|
||||
export type CreateRoleMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
@@ -183,13 +183,13 @@ export const useCreateRole = <
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createRole>>,
|
||||
TError,
|
||||
{ data: BodyType<RoletypesPostableRoleDTO> },
|
||||
{ data: BodyType<AuthtypesPostableRoleDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof createRole>>,
|
||||
TError,
|
||||
{ data: BodyType<RoletypesPostableRoleDTO> },
|
||||
{ data: BodyType<AuthtypesPostableRoleDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getCreateRoleMutationOptions(options);
|
||||
@@ -370,13 +370,13 @@ export const invalidateGetRole = async (
|
||||
*/
|
||||
export const patchRole = (
|
||||
{ id }: PatchRolePathParameters,
|
||||
roletypesPatchableRoleDTO: BodyType<RoletypesPatchableRoleDTO>,
|
||||
authtypesPatchableRoleDTO: BodyType<AuthtypesPatchableRoleDTO>,
|
||||
) => {
|
||||
return GeneratedAPIInstance<string>({
|
||||
url: `/api/v1/roles/${id}`,
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: roletypesPatchableRoleDTO,
|
||||
data: authtypesPatchableRoleDTO,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -389,7 +389,7 @@ export const getPatchRoleMutationOptions = <
|
||||
TError,
|
||||
{
|
||||
pathParams: PatchRolePathParameters;
|
||||
data: BodyType<RoletypesPatchableRoleDTO>;
|
||||
data: BodyType<AuthtypesPatchableRoleDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
@@ -398,7 +398,7 @@ export const getPatchRoleMutationOptions = <
|
||||
TError,
|
||||
{
|
||||
pathParams: PatchRolePathParameters;
|
||||
data: BodyType<RoletypesPatchableRoleDTO>;
|
||||
data: BodyType<AuthtypesPatchableRoleDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
@@ -415,7 +415,7 @@ export const getPatchRoleMutationOptions = <
|
||||
Awaited<ReturnType<typeof patchRole>>,
|
||||
{
|
||||
pathParams: PatchRolePathParameters;
|
||||
data: BodyType<RoletypesPatchableRoleDTO>;
|
||||
data: BodyType<AuthtypesPatchableRoleDTO>;
|
||||
}
|
||||
> = (props) => {
|
||||
const { pathParams, data } = props ?? {};
|
||||
@@ -429,7 +429,7 @@ export const getPatchRoleMutationOptions = <
|
||||
export type PatchRoleMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof patchRole>>
|
||||
>;
|
||||
export type PatchRoleMutationBody = BodyType<RoletypesPatchableRoleDTO>;
|
||||
export type PatchRoleMutationBody = BodyType<AuthtypesPatchableRoleDTO>;
|
||||
export type PatchRoleMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
@@ -444,7 +444,7 @@ export const usePatchRole = <
|
||||
TError,
|
||||
{
|
||||
pathParams: PatchRolePathParameters;
|
||||
data: BodyType<RoletypesPatchableRoleDTO>;
|
||||
data: BodyType<AuthtypesPatchableRoleDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
@@ -453,7 +453,7 @@ export const usePatchRole = <
|
||||
TError,
|
||||
{
|
||||
pathParams: PatchRolePathParameters;
|
||||
data: BodyType<RoletypesPatchableRoleDTO>;
|
||||
data: BodyType<AuthtypesPatchableRoleDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
|
||||
@@ -278,6 +278,13 @@ export interface AuthtypesPatchableObjectsDTO {
|
||||
deletions: AuthtypesGettableObjectsDTO[] | null;
|
||||
}
|
||||
|
||||
export interface AuthtypesPatchableRoleDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface AuthtypesPostableAuthDomainDTO {
|
||||
config?: AuthtypesAuthDomainConfigDTO;
|
||||
/**
|
||||
@@ -301,6 +308,17 @@ export interface AuthtypesPostableEmailPasswordSessionDTO {
|
||||
password?: string;
|
||||
}
|
||||
|
||||
export interface AuthtypesPostableRoleDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface AuthtypesPostableRotateTokenDTO {
|
||||
/**
|
||||
* @type string
|
||||
@@ -319,6 +337,39 @@ export interface AuthtypesResourceDTO {
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface AuthtypesRoleDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
createdAt?: Date;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
description: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
orgId: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
type: string;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
updatedAt?: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
@@ -2039,57 +2090,6 @@ export interface RenderErrorResponseDTO {
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface RoletypesPatchableRoleDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface RoletypesPostableRoleDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface RoletypesRoleDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
createdAt?: Date;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
description: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
orgId: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
type: string;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
updatedAt?: Date;
|
||||
}
|
||||
|
||||
export interface ServiceaccounttypesFactorAPIKeyDTO {
|
||||
/**
|
||||
* @type string
|
||||
@@ -2330,7 +2330,59 @@ export interface TelemetrytypesTelemetryFieldValuesDTO {
|
||||
stringValues?: string[];
|
||||
}
|
||||
|
||||
export interface TypesChangePasswordRequestDTO {
|
||||
export interface TypesGettableGlobalConfigDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
external_url?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
ingestion_url?: string;
|
||||
}
|
||||
|
||||
export interface TypesIdentifiableDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface TypesOrganizationDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
alias?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
createdAt?: Date;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
displayName?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
key?: number;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
updatedAt?: Date;
|
||||
}
|
||||
|
||||
export interface UsertypesChangePasswordRequestDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -2345,7 +2397,7 @@ export interface TypesChangePasswordRequestDTO {
|
||||
userId?: string;
|
||||
}
|
||||
|
||||
export interface TypesGettableAPIKeyDTO {
|
||||
export interface UsertypesGettableAPIKeyDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
@@ -2355,7 +2407,7 @@ export interface TypesGettableAPIKeyDTO {
|
||||
* @type string
|
||||
*/
|
||||
createdBy?: string;
|
||||
createdByUser?: TypesUserDTO;
|
||||
createdByUser?: UsertypesUserDTO;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
@@ -2395,32 +2447,14 @@ export interface TypesGettableAPIKeyDTO {
|
||||
* @type string
|
||||
*/
|
||||
updatedBy?: string;
|
||||
updatedByUser?: TypesUserDTO;
|
||||
updatedByUser?: UsertypesUserDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
userId?: string;
|
||||
}
|
||||
|
||||
export interface TypesGettableGlobalConfigDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
external_url?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
ingestion_url?: string;
|
||||
}
|
||||
|
||||
export interface TypesIdentifiableDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface TypesInviteDTO {
|
||||
export interface UsertypesInviteDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
@@ -2461,41 +2495,7 @@ export interface TypesInviteDTO {
|
||||
updatedAt?: Date;
|
||||
}
|
||||
|
||||
export interface TypesOrganizationDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
alias?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
createdAt?: Date;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
displayName?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
key?: number;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
updatedAt?: Date;
|
||||
}
|
||||
|
||||
export interface TypesPostableAPIKeyDTO {
|
||||
export interface UsertypesPostableAPIKeyDTO {
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
@@ -2511,7 +2511,7 @@ export interface TypesPostableAPIKeyDTO {
|
||||
role?: string;
|
||||
}
|
||||
|
||||
export interface TypesPostableAcceptInviteDTO {
|
||||
export interface UsertypesPostableAcceptInviteDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -2530,14 +2530,14 @@ export interface TypesPostableAcceptInviteDTO {
|
||||
token?: string;
|
||||
}
|
||||
|
||||
export interface TypesPostableBulkInviteRequestDTO {
|
||||
export interface UsertypesPostableBulkInviteRequestDTO {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
invites: TypesPostableInviteDTO[];
|
||||
invites: UsertypesPostableInviteDTO[];
|
||||
}
|
||||
|
||||
export interface TypesPostableForgotPasswordDTO {
|
||||
export interface UsertypesPostableForgotPasswordDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -2552,7 +2552,7 @@ export interface TypesPostableForgotPasswordDTO {
|
||||
orgId: string;
|
||||
}
|
||||
|
||||
export interface TypesPostableInviteDTO {
|
||||
export interface UsertypesPostableInviteDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -2571,7 +2571,7 @@ export interface TypesPostableInviteDTO {
|
||||
role?: string;
|
||||
}
|
||||
|
||||
export interface TypesPostableResetPasswordDTO {
|
||||
export interface UsertypesPostableResetPasswordDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -2582,7 +2582,7 @@ export interface TypesPostableResetPasswordDTO {
|
||||
token?: string;
|
||||
}
|
||||
|
||||
export interface TypesResetPasswordTokenDTO {
|
||||
export interface UsertypesResetPasswordTokenDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
@@ -2602,7 +2602,7 @@ export interface TypesResetPasswordTokenDTO {
|
||||
token?: string;
|
||||
}
|
||||
|
||||
export interface TypesStorableAPIKeyDTO {
|
||||
export interface UsertypesStorableAPIKeyDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
@@ -2647,7 +2647,7 @@ export interface TypesStorableAPIKeyDTO {
|
||||
userId?: string;
|
||||
}
|
||||
|
||||
export interface TypesUserDTO {
|
||||
export interface UsertypesUserDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
@@ -3018,7 +3018,7 @@ export type GetResetPasswordTokenPathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type GetResetPasswordToken200 = {
|
||||
data: TypesResetPasswordTokenDTO;
|
||||
data: UsertypesResetPasswordTokenDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -3037,7 +3037,7 @@ export type ListInvite200 = {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
data: TypesInviteDTO[];
|
||||
data: UsertypesInviteDTO[];
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -3045,7 +3045,7 @@ export type ListInvite200 = {
|
||||
};
|
||||
|
||||
export type CreateInvite201 = {
|
||||
data: TypesInviteDTO;
|
||||
data: UsertypesInviteDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -3059,7 +3059,7 @@ export type GetInvitePathParameters = {
|
||||
token: string;
|
||||
};
|
||||
export type GetInvite200 = {
|
||||
data: TypesInviteDTO;
|
||||
data: UsertypesInviteDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -3067,7 +3067,7 @@ export type GetInvite200 = {
|
||||
};
|
||||
|
||||
export type AcceptInvite201 = {
|
||||
data: TypesUserDTO;
|
||||
data: UsertypesUserDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -3115,7 +3115,7 @@ export type ListAPIKeys200 = {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
data: TypesGettableAPIKeyDTO[];
|
||||
data: UsertypesGettableAPIKeyDTO[];
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -3123,7 +3123,7 @@ export type ListAPIKeys200 = {
|
||||
};
|
||||
|
||||
export type CreateAPIKey201 = {
|
||||
data: TypesGettableAPIKeyDTO;
|
||||
data: UsertypesGettableAPIKeyDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -3163,7 +3163,7 @@ export type ListRoles200 = {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
data: RoletypesRoleDTO[];
|
||||
data: AuthtypesRoleDTO[];
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -3185,7 +3185,7 @@ export type GetRolePathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type GetRole200 = {
|
||||
data: RoletypesRoleDTO;
|
||||
data: AuthtypesRoleDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -3290,7 +3290,7 @@ export type ListUsers200 = {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
data: TypesUserDTO[];
|
||||
data: UsertypesUserDTO[];
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -3304,7 +3304,7 @@ export type GetUserPathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type GetUser200 = {
|
||||
data: TypesUserDTO;
|
||||
data: UsertypesUserDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -3315,7 +3315,7 @@ export type UpdateUserPathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type UpdateUser200 = {
|
||||
data: TypesUserDTO;
|
||||
data: UsertypesUserDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -3323,7 +3323,7 @@ export type UpdateUser200 = {
|
||||
};
|
||||
|
||||
export type GetMyUser200 = {
|
||||
data: TypesUserDTO;
|
||||
data: UsertypesUserDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
|
||||
@@ -38,18 +38,18 @@ import type {
|
||||
ListUsers200,
|
||||
RenderErrorResponseDTO,
|
||||
RevokeAPIKeyPathParameters,
|
||||
TypesChangePasswordRequestDTO,
|
||||
TypesPostableAcceptInviteDTO,
|
||||
TypesPostableAPIKeyDTO,
|
||||
TypesPostableBulkInviteRequestDTO,
|
||||
TypesPostableForgotPasswordDTO,
|
||||
TypesPostableInviteDTO,
|
||||
TypesPostableResetPasswordDTO,
|
||||
TypesStorableAPIKeyDTO,
|
||||
TypesUserDTO,
|
||||
UpdateAPIKeyPathParameters,
|
||||
UpdateUser200,
|
||||
UpdateUserPathParameters,
|
||||
UsertypesChangePasswordRequestDTO,
|
||||
UsertypesPostableAcceptInviteDTO,
|
||||
UsertypesPostableAPIKeyDTO,
|
||||
UsertypesPostableBulkInviteRequestDTO,
|
||||
UsertypesPostableForgotPasswordDTO,
|
||||
UsertypesPostableInviteDTO,
|
||||
UsertypesPostableResetPasswordDTO,
|
||||
UsertypesStorableAPIKeyDTO,
|
||||
UsertypesUserDTO,
|
||||
} from '../sigNoz.schemas';
|
||||
|
||||
/**
|
||||
@@ -58,14 +58,14 @@ import type {
|
||||
*/
|
||||
export const changePassword = (
|
||||
{ id }: ChangePasswordPathParameters,
|
||||
typesChangePasswordRequestDTO: BodyType<TypesChangePasswordRequestDTO>,
|
||||
usertypesChangePasswordRequestDTO: BodyType<UsertypesChangePasswordRequestDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v1/changePassword/${id}`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: typesChangePasswordRequestDTO,
|
||||
data: usertypesChangePasswordRequestDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
@@ -79,7 +79,7 @@ export const getChangePasswordMutationOptions = <
|
||||
TError,
|
||||
{
|
||||
pathParams: ChangePasswordPathParameters;
|
||||
data: BodyType<TypesChangePasswordRequestDTO>;
|
||||
data: BodyType<UsertypesChangePasswordRequestDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
@@ -88,7 +88,7 @@ export const getChangePasswordMutationOptions = <
|
||||
TError,
|
||||
{
|
||||
pathParams: ChangePasswordPathParameters;
|
||||
data: BodyType<TypesChangePasswordRequestDTO>;
|
||||
data: BodyType<UsertypesChangePasswordRequestDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
@@ -105,7 +105,7 @@ export const getChangePasswordMutationOptions = <
|
||||
Awaited<ReturnType<typeof changePassword>>,
|
||||
{
|
||||
pathParams: ChangePasswordPathParameters;
|
||||
data: BodyType<TypesChangePasswordRequestDTO>;
|
||||
data: BodyType<UsertypesChangePasswordRequestDTO>;
|
||||
}
|
||||
> = (props) => {
|
||||
const { pathParams, data } = props ?? {};
|
||||
@@ -119,7 +119,7 @@ export const getChangePasswordMutationOptions = <
|
||||
export type ChangePasswordMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof changePassword>>
|
||||
>;
|
||||
export type ChangePasswordMutationBody = BodyType<TypesChangePasswordRequestDTO>;
|
||||
export type ChangePasswordMutationBody = BodyType<UsertypesChangePasswordRequestDTO>;
|
||||
export type ChangePasswordMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
@@ -134,7 +134,7 @@ export const useChangePassword = <
|
||||
TError,
|
||||
{
|
||||
pathParams: ChangePasswordPathParameters;
|
||||
data: BodyType<TypesChangePasswordRequestDTO>;
|
||||
data: BodyType<UsertypesChangePasswordRequestDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
@@ -143,7 +143,7 @@ export const useChangePassword = <
|
||||
TError,
|
||||
{
|
||||
pathParams: ChangePasswordPathParameters;
|
||||
data: BodyType<TypesChangePasswordRequestDTO>;
|
||||
data: BodyType<UsertypesChangePasswordRequestDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
@@ -338,14 +338,14 @@ export const invalidateListInvite = async (
|
||||
* @summary Create invite
|
||||
*/
|
||||
export const createInvite = (
|
||||
typesPostableInviteDTO: BodyType<TypesPostableInviteDTO>,
|
||||
usertypesPostableInviteDTO: BodyType<UsertypesPostableInviteDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<CreateInvite201>({
|
||||
url: `/api/v1/invite`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: typesPostableInviteDTO,
|
||||
data: usertypesPostableInviteDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
@@ -357,13 +357,13 @@ export const getCreateInviteMutationOptions = <
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createInvite>>,
|
||||
TError,
|
||||
{ data: BodyType<TypesPostableInviteDTO> },
|
||||
{ data: BodyType<UsertypesPostableInviteDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createInvite>>,
|
||||
TError,
|
||||
{ data: BodyType<TypesPostableInviteDTO> },
|
||||
{ data: BodyType<UsertypesPostableInviteDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['createInvite'];
|
||||
@@ -377,7 +377,7 @@ export const getCreateInviteMutationOptions = <
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof createInvite>>,
|
||||
{ data: BodyType<TypesPostableInviteDTO> }
|
||||
{ data: BodyType<UsertypesPostableInviteDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
@@ -390,7 +390,7 @@ export const getCreateInviteMutationOptions = <
|
||||
export type CreateInviteMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof createInvite>>
|
||||
>;
|
||||
export type CreateInviteMutationBody = BodyType<TypesPostableInviteDTO>;
|
||||
export type CreateInviteMutationBody = BodyType<UsertypesPostableInviteDTO>;
|
||||
export type CreateInviteMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
@@ -403,13 +403,13 @@ export const useCreateInvite = <
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createInvite>>,
|
||||
TError,
|
||||
{ data: BodyType<TypesPostableInviteDTO> },
|
||||
{ data: BodyType<UsertypesPostableInviteDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof createInvite>>,
|
||||
TError,
|
||||
{ data: BodyType<TypesPostableInviteDTO> },
|
||||
{ data: BodyType<UsertypesPostableInviteDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getCreateInviteMutationOptions(options);
|
||||
@@ -589,14 +589,14 @@ export const invalidateGetInvite = async (
|
||||
* @summary Accept invite
|
||||
*/
|
||||
export const acceptInvite = (
|
||||
typesPostableAcceptInviteDTO: BodyType<TypesPostableAcceptInviteDTO>,
|
||||
usertypesPostableAcceptInviteDTO: BodyType<UsertypesPostableAcceptInviteDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<AcceptInvite201>({
|
||||
url: `/api/v1/invite/accept`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: typesPostableAcceptInviteDTO,
|
||||
data: usertypesPostableAcceptInviteDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
@@ -608,13 +608,13 @@ export const getAcceptInviteMutationOptions = <
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof acceptInvite>>,
|
||||
TError,
|
||||
{ data: BodyType<TypesPostableAcceptInviteDTO> },
|
||||
{ data: BodyType<UsertypesPostableAcceptInviteDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof acceptInvite>>,
|
||||
TError,
|
||||
{ data: BodyType<TypesPostableAcceptInviteDTO> },
|
||||
{ data: BodyType<UsertypesPostableAcceptInviteDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['acceptInvite'];
|
||||
@@ -628,7 +628,7 @@ export const getAcceptInviteMutationOptions = <
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof acceptInvite>>,
|
||||
{ data: BodyType<TypesPostableAcceptInviteDTO> }
|
||||
{ data: BodyType<UsertypesPostableAcceptInviteDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
@@ -641,7 +641,7 @@ export const getAcceptInviteMutationOptions = <
|
||||
export type AcceptInviteMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof acceptInvite>>
|
||||
>;
|
||||
export type AcceptInviteMutationBody = BodyType<TypesPostableAcceptInviteDTO>;
|
||||
export type AcceptInviteMutationBody = BodyType<UsertypesPostableAcceptInviteDTO>;
|
||||
export type AcceptInviteMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
@@ -654,13 +654,13 @@ export const useAcceptInvite = <
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof acceptInvite>>,
|
||||
TError,
|
||||
{ data: BodyType<TypesPostableAcceptInviteDTO> },
|
||||
{ data: BodyType<UsertypesPostableAcceptInviteDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof acceptInvite>>,
|
||||
TError,
|
||||
{ data: BodyType<TypesPostableAcceptInviteDTO> },
|
||||
{ data: BodyType<UsertypesPostableAcceptInviteDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getAcceptInviteMutationOptions(options);
|
||||
@@ -672,14 +672,14 @@ export const useAcceptInvite = <
|
||||
* @summary Create bulk invite
|
||||
*/
|
||||
export const createBulkInvite = (
|
||||
typesPostableBulkInviteRequestDTO: BodyType<TypesPostableBulkInviteRequestDTO>,
|
||||
usertypesPostableBulkInviteRequestDTO: BodyType<UsertypesPostableBulkInviteRequestDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v1/invite/bulk`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: typesPostableBulkInviteRequestDTO,
|
||||
data: usertypesPostableBulkInviteRequestDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
@@ -691,13 +691,13 @@ export const getCreateBulkInviteMutationOptions = <
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createBulkInvite>>,
|
||||
TError,
|
||||
{ data: BodyType<TypesPostableBulkInviteRequestDTO> },
|
||||
{ data: BodyType<UsertypesPostableBulkInviteRequestDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createBulkInvite>>,
|
||||
TError,
|
||||
{ data: BodyType<TypesPostableBulkInviteRequestDTO> },
|
||||
{ data: BodyType<UsertypesPostableBulkInviteRequestDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['createBulkInvite'];
|
||||
@@ -711,7 +711,7 @@ export const getCreateBulkInviteMutationOptions = <
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof createBulkInvite>>,
|
||||
{ data: BodyType<TypesPostableBulkInviteRequestDTO> }
|
||||
{ data: BodyType<UsertypesPostableBulkInviteRequestDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
@@ -724,7 +724,7 @@ export const getCreateBulkInviteMutationOptions = <
|
||||
export type CreateBulkInviteMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof createBulkInvite>>
|
||||
>;
|
||||
export type CreateBulkInviteMutationBody = BodyType<TypesPostableBulkInviteRequestDTO>;
|
||||
export type CreateBulkInviteMutationBody = BodyType<UsertypesPostableBulkInviteRequestDTO>;
|
||||
export type CreateBulkInviteMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
@@ -737,13 +737,13 @@ export const useCreateBulkInvite = <
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createBulkInvite>>,
|
||||
TError,
|
||||
{ data: BodyType<TypesPostableBulkInviteRequestDTO> },
|
||||
{ data: BodyType<UsertypesPostableBulkInviteRequestDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof createBulkInvite>>,
|
||||
TError,
|
||||
{ data: BodyType<TypesPostableBulkInviteRequestDTO> },
|
||||
{ data: BodyType<UsertypesPostableBulkInviteRequestDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getCreateBulkInviteMutationOptions(options);
|
||||
@@ -841,14 +841,14 @@ export const invalidateListAPIKeys = async (
|
||||
* @summary Create api key
|
||||
*/
|
||||
export const createAPIKey = (
|
||||
typesPostableAPIKeyDTO: BodyType<TypesPostableAPIKeyDTO>,
|
||||
usertypesPostableAPIKeyDTO: BodyType<UsertypesPostableAPIKeyDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<CreateAPIKey201>({
|
||||
url: `/api/v1/pats`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: typesPostableAPIKeyDTO,
|
||||
data: usertypesPostableAPIKeyDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
@@ -860,13 +860,13 @@ export const getCreateAPIKeyMutationOptions = <
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createAPIKey>>,
|
||||
TError,
|
||||
{ data: BodyType<TypesPostableAPIKeyDTO> },
|
||||
{ data: BodyType<UsertypesPostableAPIKeyDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createAPIKey>>,
|
||||
TError,
|
||||
{ data: BodyType<TypesPostableAPIKeyDTO> },
|
||||
{ data: BodyType<UsertypesPostableAPIKeyDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['createAPIKey'];
|
||||
@@ -880,7 +880,7 @@ export const getCreateAPIKeyMutationOptions = <
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof createAPIKey>>,
|
||||
{ data: BodyType<TypesPostableAPIKeyDTO> }
|
||||
{ data: BodyType<UsertypesPostableAPIKeyDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
@@ -893,7 +893,7 @@ export const getCreateAPIKeyMutationOptions = <
|
||||
export type CreateAPIKeyMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof createAPIKey>>
|
||||
>;
|
||||
export type CreateAPIKeyMutationBody = BodyType<TypesPostableAPIKeyDTO>;
|
||||
export type CreateAPIKeyMutationBody = BodyType<UsertypesPostableAPIKeyDTO>;
|
||||
export type CreateAPIKeyMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
@@ -906,13 +906,13 @@ export const useCreateAPIKey = <
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createAPIKey>>,
|
||||
TError,
|
||||
{ data: BodyType<TypesPostableAPIKeyDTO> },
|
||||
{ data: BodyType<UsertypesPostableAPIKeyDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof createAPIKey>>,
|
||||
TError,
|
||||
{ data: BodyType<TypesPostableAPIKeyDTO> },
|
||||
{ data: BodyType<UsertypesPostableAPIKeyDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getCreateAPIKeyMutationOptions(options);
|
||||
@@ -1002,13 +1002,13 @@ export const useRevokeAPIKey = <
|
||||
*/
|
||||
export const updateAPIKey = (
|
||||
{ id }: UpdateAPIKeyPathParameters,
|
||||
typesStorableAPIKeyDTO: BodyType<TypesStorableAPIKeyDTO>,
|
||||
usertypesStorableAPIKeyDTO: BodyType<UsertypesStorableAPIKeyDTO>,
|
||||
) => {
|
||||
return GeneratedAPIInstance<string>({
|
||||
url: `/api/v1/pats/${id}`,
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: typesStorableAPIKeyDTO,
|
||||
data: usertypesStorableAPIKeyDTO,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1021,7 +1021,7 @@ export const getUpdateAPIKeyMutationOptions = <
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateAPIKeyPathParameters;
|
||||
data: BodyType<TypesStorableAPIKeyDTO>;
|
||||
data: BodyType<UsertypesStorableAPIKeyDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
@@ -1030,7 +1030,7 @@ export const getUpdateAPIKeyMutationOptions = <
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateAPIKeyPathParameters;
|
||||
data: BodyType<TypesStorableAPIKeyDTO>;
|
||||
data: BodyType<UsertypesStorableAPIKeyDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
@@ -1047,7 +1047,7 @@ export const getUpdateAPIKeyMutationOptions = <
|
||||
Awaited<ReturnType<typeof updateAPIKey>>,
|
||||
{
|
||||
pathParams: UpdateAPIKeyPathParameters;
|
||||
data: BodyType<TypesStorableAPIKeyDTO>;
|
||||
data: BodyType<UsertypesStorableAPIKeyDTO>;
|
||||
}
|
||||
> = (props) => {
|
||||
const { pathParams, data } = props ?? {};
|
||||
@@ -1061,7 +1061,7 @@ export const getUpdateAPIKeyMutationOptions = <
|
||||
export type UpdateAPIKeyMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof updateAPIKey>>
|
||||
>;
|
||||
export type UpdateAPIKeyMutationBody = BodyType<TypesStorableAPIKeyDTO>;
|
||||
export type UpdateAPIKeyMutationBody = BodyType<UsertypesStorableAPIKeyDTO>;
|
||||
export type UpdateAPIKeyMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
@@ -1076,7 +1076,7 @@ export const useUpdateAPIKey = <
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateAPIKeyPathParameters;
|
||||
data: BodyType<TypesStorableAPIKeyDTO>;
|
||||
data: BodyType<UsertypesStorableAPIKeyDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
@@ -1085,7 +1085,7 @@ export const useUpdateAPIKey = <
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateAPIKeyPathParameters;
|
||||
data: BodyType<TypesStorableAPIKeyDTO>;
|
||||
data: BodyType<UsertypesStorableAPIKeyDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
@@ -1098,14 +1098,14 @@ export const useUpdateAPIKey = <
|
||||
* @summary Reset password
|
||||
*/
|
||||
export const resetPassword = (
|
||||
typesPostableResetPasswordDTO: BodyType<TypesPostableResetPasswordDTO>,
|
||||
usertypesPostableResetPasswordDTO: BodyType<UsertypesPostableResetPasswordDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v1/resetPassword`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: typesPostableResetPasswordDTO,
|
||||
data: usertypesPostableResetPasswordDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
@@ -1117,13 +1117,13 @@ export const getResetPasswordMutationOptions = <
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof resetPassword>>,
|
||||
TError,
|
||||
{ data: BodyType<TypesPostableResetPasswordDTO> },
|
||||
{ data: BodyType<UsertypesPostableResetPasswordDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof resetPassword>>,
|
||||
TError,
|
||||
{ data: BodyType<TypesPostableResetPasswordDTO> },
|
||||
{ data: BodyType<UsertypesPostableResetPasswordDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['resetPassword'];
|
||||
@@ -1137,7 +1137,7 @@ export const getResetPasswordMutationOptions = <
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof resetPassword>>,
|
||||
{ data: BodyType<TypesPostableResetPasswordDTO> }
|
||||
{ data: BodyType<UsertypesPostableResetPasswordDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
@@ -1150,7 +1150,7 @@ export const getResetPasswordMutationOptions = <
|
||||
export type ResetPasswordMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof resetPassword>>
|
||||
>;
|
||||
export type ResetPasswordMutationBody = BodyType<TypesPostableResetPasswordDTO>;
|
||||
export type ResetPasswordMutationBody = BodyType<UsertypesPostableResetPasswordDTO>;
|
||||
export type ResetPasswordMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
@@ -1163,13 +1163,13 @@ export const useResetPassword = <
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof resetPassword>>,
|
||||
TError,
|
||||
{ data: BodyType<TypesPostableResetPasswordDTO> },
|
||||
{ data: BodyType<UsertypesPostableResetPasswordDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof resetPassword>>,
|
||||
TError,
|
||||
{ data: BodyType<TypesPostableResetPasswordDTO> },
|
||||
{ data: BodyType<UsertypesPostableResetPasswordDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getResetPasswordMutationOptions(options);
|
||||
@@ -1428,13 +1428,13 @@ export const invalidateGetUser = async (
|
||||
*/
|
||||
export const updateUser = (
|
||||
{ id }: UpdateUserPathParameters,
|
||||
typesUserDTO: BodyType<TypesUserDTO>,
|
||||
usertypesUserDTO: BodyType<UsertypesUserDTO>,
|
||||
) => {
|
||||
return GeneratedAPIInstance<UpdateUser200>({
|
||||
url: `/api/v1/user/${id}`,
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: typesUserDTO,
|
||||
data: usertypesUserDTO,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1445,13 +1445,13 @@ export const getUpdateUserMutationOptions = <
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateUser>>,
|
||||
TError,
|
||||
{ pathParams: UpdateUserPathParameters; data: BodyType<TypesUserDTO> },
|
||||
{ pathParams: UpdateUserPathParameters; data: BodyType<UsertypesUserDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateUser>>,
|
||||
TError,
|
||||
{ pathParams: UpdateUserPathParameters; data: BodyType<TypesUserDTO> },
|
||||
{ pathParams: UpdateUserPathParameters; data: BodyType<UsertypesUserDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['updateUser'];
|
||||
@@ -1465,7 +1465,7 @@ export const getUpdateUserMutationOptions = <
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof updateUser>>,
|
||||
{ pathParams: UpdateUserPathParameters; data: BodyType<TypesUserDTO> }
|
||||
{ pathParams: UpdateUserPathParameters; data: BodyType<UsertypesUserDTO> }
|
||||
> = (props) => {
|
||||
const { pathParams, data } = props ?? {};
|
||||
|
||||
@@ -1478,7 +1478,7 @@ export const getUpdateUserMutationOptions = <
|
||||
export type UpdateUserMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof updateUser>>
|
||||
>;
|
||||
export type UpdateUserMutationBody = BodyType<TypesUserDTO>;
|
||||
export type UpdateUserMutationBody = BodyType<UsertypesUserDTO>;
|
||||
export type UpdateUserMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
@@ -1491,13 +1491,13 @@ export const useUpdateUser = <
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateUser>>,
|
||||
TError,
|
||||
{ pathParams: UpdateUserPathParameters; data: BodyType<TypesUserDTO> },
|
||||
{ pathParams: UpdateUserPathParameters; data: BodyType<UsertypesUserDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof updateUser>>,
|
||||
TError,
|
||||
{ pathParams: UpdateUserPathParameters; data: BodyType<TypesUserDTO> },
|
||||
{ pathParams: UpdateUserPathParameters; data: BodyType<UsertypesUserDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getUpdateUserMutationOptions(options);
|
||||
@@ -1587,14 +1587,14 @@ export const invalidateGetMyUser = async (
|
||||
* @summary Forgot password
|
||||
*/
|
||||
export const forgotPassword = (
|
||||
typesPostableForgotPasswordDTO: BodyType<TypesPostableForgotPasswordDTO>,
|
||||
usertypesPostableForgotPasswordDTO: BodyType<UsertypesPostableForgotPasswordDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v2/factor_password/forgot`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: typesPostableForgotPasswordDTO,
|
||||
data: usertypesPostableForgotPasswordDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
@@ -1606,13 +1606,13 @@ export const getForgotPasswordMutationOptions = <
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof forgotPassword>>,
|
||||
TError,
|
||||
{ data: BodyType<TypesPostableForgotPasswordDTO> },
|
||||
{ data: BodyType<UsertypesPostableForgotPasswordDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof forgotPassword>>,
|
||||
TError,
|
||||
{ data: BodyType<TypesPostableForgotPasswordDTO> },
|
||||
{ data: BodyType<UsertypesPostableForgotPasswordDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['forgotPassword'];
|
||||
@@ -1626,7 +1626,7 @@ export const getForgotPasswordMutationOptions = <
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof forgotPassword>>,
|
||||
{ data: BodyType<TypesPostableForgotPasswordDTO> }
|
||||
{ data: BodyType<UsertypesPostableForgotPasswordDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
@@ -1639,7 +1639,7 @@ export const getForgotPasswordMutationOptions = <
|
||||
export type ForgotPasswordMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof forgotPassword>>
|
||||
>;
|
||||
export type ForgotPasswordMutationBody = BodyType<TypesPostableForgotPasswordDTO>;
|
||||
export type ForgotPasswordMutationBody = BodyType<UsertypesPostableForgotPasswordDTO>;
|
||||
export type ForgotPasswordMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
@@ -1652,13 +1652,13 @@ export const useForgotPassword = <
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof forgotPassword>>,
|
||||
TError,
|
||||
{ data: BodyType<TypesPostableForgotPasswordDTO> },
|
||||
{ data: BodyType<UsertypesPostableForgotPasswordDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof forgotPassword>>,
|
||||
TError,
|
||||
{ data: BodyType<TypesPostableForgotPasswordDTO> },
|
||||
{ data: BodyType<UsertypesPostableForgotPasswordDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getForgotPasswordMutationOptions(options);
|
||||
|
||||
@@ -13,8 +13,8 @@ import {
|
||||
usePatchRole,
|
||||
} from 'api/generated/services/role';
|
||||
import {
|
||||
AuthtypesPostableRoleDTO,
|
||||
RenderErrorResponseDTO,
|
||||
RoletypesPostableRoleDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { ErrorType } from 'api/generatedAPIInstance';
|
||||
import ROUTES from 'constants/routes';
|
||||
@@ -114,7 +114,7 @@ function CreateRoleModal({
|
||||
data: { description: values.description || '' },
|
||||
});
|
||||
} else {
|
||||
const data: RoletypesPostableRoleDTO = {
|
||||
const data: AuthtypesPostableRoleDTO = {
|
||||
name: values.name,
|
||||
...(values.description ? { description: values.description } : {}),
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { Pagination, Skeleton } from 'antd';
|
||||
import { useListRoles } from 'api/generated/services/role';
|
||||
import { RoletypesRoleDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { AuthtypesRoleDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import ErrorInPlace from 'components/ErrorInPlace/ErrorInPlace';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import ROUTES from 'constants/routes';
|
||||
@@ -20,7 +20,7 @@ const PAGE_SIZE = 20;
|
||||
|
||||
type DisplayItem =
|
||||
| { type: 'section'; label: string; count?: number }
|
||||
| { type: 'role'; role: RoletypesRoleDTO };
|
||||
| { type: 'role'; role: AuthtypesRoleDTO };
|
||||
|
||||
interface RolesListingTableProps {
|
||||
searchQuery: string;
|
||||
@@ -187,7 +187,7 @@ function RolesListingTable({
|
||||
};
|
||||
|
||||
// todo: use table from periscope when its available for consumption
|
||||
const renderRow = (role: RoletypesRoleDTO): JSX.Element => (
|
||||
const renderRow = (role: AuthtypesRoleDTO): JSX.Element => (
|
||||
<div
|
||||
key={role.id}
|
||||
className={`roles-table-row ${
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { RoletypesRoleDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { AuthtypesRoleDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
const orgId = '019ba2bb-2fa1-7b24-8159-cfca08617ef9';
|
||||
|
||||
export const managedRoles: RoletypesRoleDTO[] = [
|
||||
export const managedRoles: AuthtypesRoleDTO[] = [
|
||||
{
|
||||
id: '019c24aa-2248-756f-9833-984f1ab63819',
|
||||
createdAt: new Date('2026-02-03T18:00:55.624356Z'),
|
||||
@@ -35,7 +35,7 @@ export const managedRoles: RoletypesRoleDTO[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export const customRoles: RoletypesRoleDTO[] = [
|
||||
export const customRoles: AuthtypesRoleDTO[] = [
|
||||
{
|
||||
id: '019c24aa-3333-0001-aaaa-111111111111',
|
||||
createdAt: new Date('2026-02-10T10:30:00.000Z'),
|
||||
@@ -56,7 +56,7 @@ export const customRoles: RoletypesRoleDTO[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export const allRoles: RoletypesRoleDTO[] = [...managedRoles, ...customRoles];
|
||||
export const allRoles: AuthtypesRoleDTO[] = [...managedRoles, ...customRoles];
|
||||
|
||||
export const listRolesSuccessResponse = {
|
||||
status: 'success',
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { sentryVitePlugin } from '@sentry/vite-plugin';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { readFileSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
import { visualizer } from 'rollup-plugin-visualizer';
|
||||
import type { Plugin, TransformResult, UserConfig } from 'vite';
|
||||
import type { Plugin, UserConfig } from 'vite';
|
||||
import { defineConfig, loadEnv } from 'vite';
|
||||
import vitePluginChecker from 'vite-plugin-checker';
|
||||
import viteCompression from 'vite-plugin-compression';
|
||||
@@ -13,14 +14,15 @@ import tsconfigPaths from 'vite-tsconfig-paths';
|
||||
function rawMarkdownPlugin(): Plugin {
|
||||
return {
|
||||
name: 'raw-markdown',
|
||||
transform(code, id): TransformResult | undefined {
|
||||
if (!id.endsWith('.md')) {
|
||||
return undefined;
|
||||
transform(_, id): any {
|
||||
if (id.endsWith('.md')) {
|
||||
const content = readFileSync(id, 'utf-8');
|
||||
return {
|
||||
code: `export default ${JSON.stringify(content)};`,
|
||||
map: null,
|
||||
};
|
||||
}
|
||||
return {
|
||||
code: `export default ${JSON.stringify(code)};`,
|
||||
map: null,
|
||||
};
|
||||
return undefined;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -69,7 +71,7 @@ export default defineConfig(
|
||||
);
|
||||
}
|
||||
|
||||
if (mode === 'production') {
|
||||
if (env.NODE_ENV === 'production') {
|
||||
plugins.push(
|
||||
ViteImageOptimizer({
|
||||
jpeg: { quality: 80 },
|
||||
@@ -100,25 +102,22 @@ export default defineConfig(
|
||||
},
|
||||
define: {
|
||||
// TODO: Remove this in favor of import.meta.env
|
||||
'process.env.NODE_ENV': JSON.stringify(mode),
|
||||
'process.env.FRONTEND_API_ENDPOINT': JSON.stringify(
|
||||
env.VITE_FRONTEND_API_ENDPOINT,
|
||||
),
|
||||
'process.env.WEBSOCKET_API_ENDPOINT': JSON.stringify(
|
||||
env.VITE_WEBSOCKET_API_ENDPOINT,
|
||||
),
|
||||
'process.env.PYLON_APP_ID': JSON.stringify(env.VITE_PYLON_APP_ID),
|
||||
'process.env.PYLON_IDENTITY_SECRET': JSON.stringify(
|
||||
env.VITE_PYLON_IDENTITY_SECRET,
|
||||
),
|
||||
'process.env.APPCUES_APP_ID': JSON.stringify(env.VITE_APPCUES_APP_ID),
|
||||
'process.env.POSTHOG_KEY': JSON.stringify(env.VITE_POSTHOG_KEY),
|
||||
'process.env.SENTRY_ORG': JSON.stringify(env.VITE_SENTRY_ORG),
|
||||
'process.env.SENTRY_PROJECT_ID': JSON.stringify(env.VITE_SENTRY_PROJECT_ID),
|
||||
'process.env.SENTRY_DSN': JSON.stringify(env.VITE_SENTRY_DSN),
|
||||
'process.env.TUNNEL_URL': JSON.stringify(env.VITE_TUNNEL_URL),
|
||||
'process.env.TUNNEL_DOMAIN': JSON.stringify(env.VITE_TUNNEL_DOMAIN),
|
||||
'process.env.DOCS_BASE_URL': JSON.stringify(env.VITE_DOCS_BASE_URL),
|
||||
'process.env': JSON.stringify({
|
||||
NODE_ENV: mode,
|
||||
FRONTEND_API_ENDPOINT: env.VITE_FRONTEND_API_ENDPOINT,
|
||||
WEBSOCKET_API_ENDPOINT: env.VITE_WEBSOCKET_API_ENDPOINT,
|
||||
PYLON_APP_ID: env.VITE_PYLON_APP_ID,
|
||||
PYLON_IDENTITY_SECRET: env.VITE_PYLON_IDENTITY_SECRET,
|
||||
APPCUES_APP_ID: env.VITE_APPCUES_APP_ID,
|
||||
POSTHOG_KEY: env.VITE_POSTHOG_KEY,
|
||||
SENTRY_AUTH_TOKEN: env.VITE_SENTRY_AUTH_TOKEN,
|
||||
SENTRY_ORG: env.VITE_SENTRY_ORG,
|
||||
SENTRY_PROJECT_ID: env.VITE_SENTRY_PROJECT_ID,
|
||||
SENTRY_DSN: env.VITE_SENTRY_DSN,
|
||||
TUNNEL_URL: env.VITE_TUNNEL_URL,
|
||||
TUNNEL_DOMAIN: env.VITE_TUNNEL_DOMAIN,
|
||||
DOCS_BASE_URL: env.VITE_DOCS_BASE_URL,
|
||||
}),
|
||||
},
|
||||
build: {
|
||||
sourcemap: true,
|
||||
|
||||
5
go.mod
5
go.mod
@@ -16,7 +16,6 @@ require (
|
||||
github.com/coreos/go-oidc/v3 v3.17.0
|
||||
github.com/dgraph-io/ristretto/v2 v2.3.0
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/emersion/go-smtp v0.24.0
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
github.com/go-co-op/gocron v1.30.1
|
||||
github.com/go-openapi/runtime v0.29.2
|
||||
@@ -65,7 +64,6 @@ require (
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.2.9
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.2.9
|
||||
github.com/uptrace/bun/extra/bunotel v1.2.9
|
||||
github.com/yuin/goldmark v1.7.16
|
||||
go.opentelemetry.io/collector/confmap v1.51.0
|
||||
go.opentelemetry.io/collector/otelcol v0.144.0
|
||||
go.opentelemetry.io/collector/pdata v1.51.0
|
||||
@@ -110,7 +108,6 @@ require (
|
||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/go-openapi/swag/cmdutils v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/conv v0.25.4 // indirect
|
||||
@@ -164,7 +161,7 @@ require (
|
||||
github.com/ClickHouse/ch-go v0.67.0 // indirect
|
||||
github.com/Masterminds/squirrel v1.5.4 // indirect
|
||||
github.com/Yiling-J/theine-go v0.6.2 // indirect
|
||||
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b
|
||||
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/armon/go-metrics v0.4.1 // indirect
|
||||
github.com/beevik/etree v1.1.0 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@@ -1142,8 +1142,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE=
|
||||
github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/zeebo/assert v1.3.1 h1:vukIABvugfNMZMQO1ABsyQDJDTVQbn+LWSMy1ol1h6A=
|
||||
|
||||
@@ -1,430 +0,0 @@
|
||||
package email
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"math/rand"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"mime/quotedprintable"
|
||||
"net"
|
||||
"net/mail"
|
||||
"net/smtp"
|
||||
"net/textproto"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
commoncfg "github.com/prometheus/common/config"
|
||||
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
)
|
||||
|
||||
const (
|
||||
Integration = "email"
|
||||
)
|
||||
|
||||
// Email implements a Notifier for email notifications.
|
||||
type Email struct {
|
||||
conf *config.EmailConfig
|
||||
tmpl *template.Template
|
||||
logger *slog.Logger
|
||||
hostname string
|
||||
}
|
||||
|
||||
var errNoAuthUserNameConfigured = errors.NewInternalf(errors.CodeInternal, "no auth username configured")
|
||||
|
||||
// New returns a new Email notifier.
|
||||
func New(c *config.EmailConfig, t *template.Template, l *slog.Logger) *Email {
|
||||
if _, ok := c.Headers["Subject"]; !ok {
|
||||
c.Headers["Subject"] = config.DefaultEmailSubject
|
||||
}
|
||||
if _, ok := c.Headers["To"]; !ok {
|
||||
c.Headers["To"] = c.To
|
||||
}
|
||||
if _, ok := c.Headers["From"]; !ok {
|
||||
c.Headers["From"] = c.From
|
||||
}
|
||||
|
||||
h, err := os.Hostname()
|
||||
// If we can't get the hostname, we'll use localhost
|
||||
if err != nil {
|
||||
h = "localhost.localdomain"
|
||||
}
|
||||
return &Email{conf: c, tmpl: t, logger: l, hostname: h}
|
||||
}
|
||||
|
||||
// auth resolves a string of authentication mechanisms.
|
||||
func (n *Email) auth(mechs string) (smtp.Auth, error) {
|
||||
username := n.conf.AuthUsername
|
||||
|
||||
// If no username is set, return custom error which can be ignored if needed.
|
||||
if n.conf.AuthUsername == "" {
|
||||
return nil, errNoAuthUserNameConfigured
|
||||
}
|
||||
|
||||
err := &types.MultiError{}
|
||||
for mech := range strings.SplitSeq(mechs, " ") {
|
||||
switch mech {
|
||||
case "CRAM-MD5":
|
||||
secret, secretErr := n.getAuthSecret()
|
||||
if secretErr != nil {
|
||||
err.Add(secretErr)
|
||||
continue
|
||||
}
|
||||
if secret == "" {
|
||||
err.Add(errors.NewInternalf(errors.CodeInternal, "missing secret for CRAM-MD5 auth mechanism"))
|
||||
continue
|
||||
}
|
||||
return smtp.CRAMMD5Auth(username, secret), nil
|
||||
|
||||
case "PLAIN":
|
||||
password, passwordErr := n.getPassword()
|
||||
if passwordErr != nil {
|
||||
err.Add(passwordErr)
|
||||
continue
|
||||
}
|
||||
if password == "" {
|
||||
err.Add(errors.NewInternalf(errors.CodeInternal, "missing password for PLAIN auth mechanism"))
|
||||
continue
|
||||
}
|
||||
identity := n.conf.AuthIdentity
|
||||
|
||||
return smtp.PlainAuth(identity, username, password, n.conf.Smarthost.Host), nil
|
||||
case "LOGIN":
|
||||
password, passwordErr := n.getPassword()
|
||||
if passwordErr != nil {
|
||||
err.Add(passwordErr)
|
||||
continue
|
||||
}
|
||||
if password == "" {
|
||||
err.Add(errors.NewInternalf(errors.CodeInternal, "missing password for LOGIN auth mechanism"))
|
||||
continue
|
||||
}
|
||||
return LoginAuth(username, password), nil
|
||||
}
|
||||
}
|
||||
if err.Len() == 0 {
|
||||
err.Add(errors.NewInternalf(errors.CodeInternal, "unknown auth mechanism: %s", mechs))
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Notify implements the Notifier interface.
|
||||
func (n *Email) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
|
||||
var (
|
||||
c *smtp.Client
|
||||
conn net.Conn
|
||||
err error
|
||||
success = false
|
||||
)
|
||||
// Determine whether to use Implicit TLS
|
||||
var useImplicitTLS bool
|
||||
if n.conf.ForceImplicitTLS != nil {
|
||||
useImplicitTLS = *n.conf.ForceImplicitTLS
|
||||
} else {
|
||||
// Default logic: port 465 uses implicit TLS (backward compatibility)
|
||||
useImplicitTLS = n.conf.Smarthost.Port == "465"
|
||||
}
|
||||
|
||||
if useImplicitTLS {
|
||||
tlsConfig, err := commoncfg.NewTLSConfig(n.conf.TLSConfig)
|
||||
if err != nil {
|
||||
return false, errors.WrapInternalf(err, errors.CodeInternal, "parse TLS configuration")
|
||||
}
|
||||
if tlsConfig.ServerName == "" {
|
||||
tlsConfig.ServerName = n.conf.Smarthost.Host
|
||||
}
|
||||
|
||||
conn, err = tls.Dial("tcp", n.conf.Smarthost.String(), tlsConfig)
|
||||
if err != nil {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "establish TLS connection to server")
|
||||
}
|
||||
} else {
|
||||
var (
|
||||
d = net.Dialer{}
|
||||
err error
|
||||
)
|
||||
conn, err = d.DialContext(ctx, "tcp", n.conf.Smarthost.String())
|
||||
if err != nil {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "establish connection to server")
|
||||
}
|
||||
}
|
||||
c, err = smtp.NewClient(conn, n.conf.Smarthost.Host)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "create SMTP client")
|
||||
}
|
||||
defer func() {
|
||||
// Try to clean up after ourselves but don't log anything if something has failed.
|
||||
if err := c.Quit(); success && err != nil {
|
||||
n.logger.WarnContext(ctx, "failed to close SMTP connection", "err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if n.conf.Hello != "" {
|
||||
err = c.Hello(n.conf.Hello)
|
||||
if err != nil {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "send EHLO command")
|
||||
}
|
||||
}
|
||||
|
||||
// Global Config guarantees RequireTLS is not nil.
|
||||
if *n.conf.RequireTLS && !useImplicitTLS {
|
||||
if ok, _ := c.Extension("STARTTLS"); !ok {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "'require_tls' is true (default) but %q does not advertise the STARTTLS extension", n.conf.Smarthost)
|
||||
}
|
||||
|
||||
tlsConf, err := commoncfg.NewTLSConfig(n.conf.TLSConfig)
|
||||
if err != nil {
|
||||
return false, errors.WrapInternalf(err, errors.CodeInternal, "parse TLS configuration")
|
||||
}
|
||||
if tlsConf.ServerName == "" {
|
||||
tlsConf.ServerName = n.conf.Smarthost.Host
|
||||
}
|
||||
|
||||
if err := c.StartTLS(tlsConf); err != nil {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "send STARTTLS command")
|
||||
}
|
||||
}
|
||||
|
||||
if ok, mech := c.Extension("AUTH"); ok {
|
||||
auth, err := n.auth(mech)
|
||||
if err != nil && err != errNoAuthUserNameConfigured {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "find auth mechanism")
|
||||
} else if err == errNoAuthUserNameConfigured {
|
||||
n.logger.DebugContext(ctx, "no auth username configured. Attempting to send email without authenticating")
|
||||
}
|
||||
if auth != nil {
|
||||
if err := c.Auth(auth); err != nil {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "%T auth", auth)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
tmplErr error
|
||||
data = notify.GetTemplateData(ctx, n.tmpl, as, n.logger)
|
||||
tmpl = notify.TmplText(n.tmpl, data, &tmplErr)
|
||||
)
|
||||
from := tmpl(n.conf.From)
|
||||
if tmplErr != nil {
|
||||
return false, errors.WrapInternalf(tmplErr, errors.CodeInternal, "execute 'from' template")
|
||||
}
|
||||
to := tmpl(n.conf.To)
|
||||
if tmplErr != nil {
|
||||
return false, errors.WrapInternalf(tmplErr, errors.CodeInternal, "execute 'to' template")
|
||||
}
|
||||
|
||||
addrs, err := mail.ParseAddressList(from)
|
||||
if err != nil {
|
||||
return false, errors.WrapInternalf(err, errors.CodeInternal, "parse 'from' addresses")
|
||||
}
|
||||
if len(addrs) != 1 {
|
||||
return false, errors.NewInternalf(errors.CodeInternal, "must be exactly one 'from' address (got: %d)", len(addrs))
|
||||
}
|
||||
if err = c.Mail(addrs[0].Address); err != nil {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "send MAIL command")
|
||||
}
|
||||
addrs, err = mail.ParseAddressList(to)
|
||||
if err != nil {
|
||||
return false, errors.WrapInternalf(err, errors.CodeInternal, "parse 'to' addresses")
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
if err = c.Rcpt(addr.Address); err != nil {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "send RCPT command")
|
||||
}
|
||||
}
|
||||
|
||||
// Send the email headers and body.
|
||||
message, err := c.Data()
|
||||
if err != nil {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "send DATA command")
|
||||
}
|
||||
closeOnce := sync.OnceValue(func() error {
|
||||
return message.Close()
|
||||
})
|
||||
// Close the message when this method exits in order to not leak resources. Even though we're calling this explicitly
|
||||
// further down, the method may exit before then.
|
||||
defer func() {
|
||||
// If we try close an already-closed writer, it'll send a subsequent request to the server which is invalid.
|
||||
_ = closeOnce()
|
||||
}()
|
||||
|
||||
buffer := &bytes.Buffer{}
|
||||
for header, t := range n.conf.Headers {
|
||||
value, err := n.tmpl.ExecuteTextString(t, data)
|
||||
if err != nil {
|
||||
return false, errors.WrapInternalf(err, errors.CodeInternal, "execute %q header template", header)
|
||||
}
|
||||
fmt.Fprintf(buffer, "%s: %s\r\n", header, mime.QEncoding.Encode("utf-8", value))
|
||||
}
|
||||
|
||||
if _, ok := n.conf.Headers["Message-Id"]; !ok {
|
||||
fmt.Fprintf(buffer, "Message-Id: %s\r\n", fmt.Sprintf("<%d.%d@%s>", time.Now().UnixNano(), rand.Uint64(), n.hostname))
|
||||
}
|
||||
|
||||
if n.conf.Threading.Enabled {
|
||||
key, err := notify.ExtractGroupKey(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// Add threading headers. All notifications for the same alert group
|
||||
// (identified by key hash) are threaded together.
|
||||
threadBy := ""
|
||||
if n.conf.Threading.ThreadByDate != "none" {
|
||||
// ThreadByDate is 'daily':
|
||||
// Use current date so all mails for this alert today thread together.
|
||||
threadBy = time.Now().Format("2006-01-02")
|
||||
}
|
||||
keyHash := key.Hash()
|
||||
if len(keyHash) > 16 {
|
||||
keyHash = keyHash[:16]
|
||||
}
|
||||
// The thread root ID is a Message-ID that doesn't correspond to
|
||||
// any actual email. Email clients following the (commonly used) JWZ
|
||||
// algorithm will create a dummy container to group these messages.
|
||||
threadRootID := fmt.Sprintf("<alert-%s-%s@alertmanager>", keyHash, threadBy)
|
||||
fmt.Fprintf(buffer, "References: %s\r\n", threadRootID)
|
||||
fmt.Fprintf(buffer, "In-Reply-To: %s\r\n", threadRootID)
|
||||
}
|
||||
|
||||
multipartBuffer := &bytes.Buffer{}
|
||||
multipartWriter := multipart.NewWriter(multipartBuffer)
|
||||
|
||||
fmt.Fprintf(buffer, "Date: %s\r\n", time.Now().Format(time.RFC1123Z))
|
||||
fmt.Fprintf(buffer, "Content-Type: multipart/alternative; boundary=%s\r\n", multipartWriter.Boundary())
|
||||
fmt.Fprintf(buffer, "MIME-Version: 1.0\r\n\r\n")
|
||||
|
||||
// TODO: Add some useful headers here, such as URL of the alertmanager
|
||||
// and active/resolved.
|
||||
_, err = message.Write(buffer.Bytes())
|
||||
if err != nil {
|
||||
return false, errors.WrapInternalf(err, errors.CodeInternal, "write headers")
|
||||
}
|
||||
|
||||
if len(n.conf.Text) > 0 {
|
||||
// Text template
|
||||
w, err := multipartWriter.CreatePart(textproto.MIMEHeader{
|
||||
"Content-Transfer-Encoding": {"quoted-printable"},
|
||||
"Content-Type": {"text/plain; charset=UTF-8"},
|
||||
})
|
||||
if err != nil {
|
||||
return false, errors.WrapInternalf(err, errors.CodeInternal, "create part for text template")
|
||||
}
|
||||
body, err := n.tmpl.ExecuteTextString(n.conf.Text, data)
|
||||
if err != nil {
|
||||
return false, errors.WrapInternalf(err, errors.CodeInternal, "execute text template")
|
||||
}
|
||||
qw := quotedprintable.NewWriter(w)
|
||||
_, err = qw.Write([]byte(body))
|
||||
if err != nil {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "write text part")
|
||||
}
|
||||
err = qw.Close()
|
||||
if err != nil {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "close text part")
|
||||
}
|
||||
}
|
||||
|
||||
if len(n.conf.HTML) > 0 {
|
||||
// Html template
|
||||
// Preferred alternative placed last per section 5.1.4 of RFC 2046
|
||||
// https://www.ietf.org/rfc/rfc2046.txt
|
||||
w, err := multipartWriter.CreatePart(textproto.MIMEHeader{
|
||||
"Content-Transfer-Encoding": {"quoted-printable"},
|
||||
"Content-Type": {"text/html; charset=UTF-8"},
|
||||
})
|
||||
if err != nil {
|
||||
return false, errors.WrapInternalf(err, errors.CodeInternal, "create part for html template")
|
||||
}
|
||||
body, err := n.tmpl.ExecuteHTMLString(n.conf.HTML, data)
|
||||
if err != nil {
|
||||
return false, errors.WrapInternalf(err, errors.CodeInternal, "execute html template")
|
||||
}
|
||||
qw := quotedprintable.NewWriter(w)
|
||||
_, err = qw.Write([]byte(body))
|
||||
if err != nil {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "write HTML part")
|
||||
}
|
||||
err = qw.Close()
|
||||
if err != nil {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "close HTML part")
|
||||
}
|
||||
}
|
||||
|
||||
err = multipartWriter.Close()
|
||||
if err != nil {
|
||||
return false, errors.WrapInternalf(err, errors.CodeInternal, "close multipartWriter")
|
||||
}
|
||||
|
||||
_, err = message.Write(multipartBuffer.Bytes())
|
||||
if err != nil {
|
||||
return false, errors.WrapInternalf(err, errors.CodeInternal, "write body buffer")
|
||||
}
|
||||
|
||||
// Complete the message and await response.
|
||||
if err = closeOnce(); err != nil {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "delivery failure")
|
||||
}
|
||||
|
||||
success = true
|
||||
return false, nil
|
||||
}
|
||||
|
||||
type loginAuth struct {
|
||||
username, password string
|
||||
}
|
||||
|
||||
func LoginAuth(username, password string) smtp.Auth {
|
||||
return &loginAuth{username, password}
|
||||
}
|
||||
|
||||
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
|
||||
return "LOGIN", []byte{}, nil
|
||||
}
|
||||
|
||||
// Used for AUTH LOGIN. (Maybe password should be encrypted).
|
||||
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
|
||||
if more {
|
||||
switch strings.ToLower(string(fromServer)) {
|
||||
case "username:":
|
||||
return []byte(a.username), nil
|
||||
case "password:":
|
||||
return []byte(a.password), nil
|
||||
default:
|
||||
return nil, errors.NewInternalf(errors.CodeInternal, "unexpected server challenge")
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (n *Email) getPassword() (string, error) {
|
||||
if len(n.conf.AuthPasswordFile) > 0 {
|
||||
content, err := os.ReadFile(n.conf.AuthPasswordFile)
|
||||
if err != nil {
|
||||
return "", errors.NewInternalf(errors.CodeInternal, "could not read %s: %v", n.conf.AuthPasswordFile, err)
|
||||
}
|
||||
return strings.TrimSpace(string(content)), nil
|
||||
}
|
||||
return string(n.conf.AuthPassword), nil
|
||||
}
|
||||
|
||||
func (n *Email) getAuthSecret() (string, error) {
|
||||
if len(n.conf.AuthSecretFile) > 0 {
|
||||
content, err := os.ReadFile(n.conf.AuthSecretFile)
|
||||
if err != nil {
|
||||
return "", errors.NewInternalf(errors.CodeInternal, "could not read %s: %v", n.conf.AuthSecretFile, err)
|
||||
}
|
||||
return string(content), nil
|
||||
}
|
||||
return string(n.conf.AuthSecret), nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +0,0 @@
|
||||
smarthost: 127.0.0.1:1026
|
||||
server: http://127.0.0.1:1081/
|
||||
username: user
|
||||
password: pass
|
||||
@@ -1,4 +0,0 @@
|
||||
smarthost: maildev-auth:1025
|
||||
server: http://maildev-auth:1080/
|
||||
username: user
|
||||
password: pass
|
||||
@@ -1,2 +0,0 @@
|
||||
smarthost: 127.0.0.1:1025
|
||||
server: http://127.0.0.1:1080/
|
||||
@@ -1,2 +0,0 @@
|
||||
smarthost: maildev-noauth:1025
|
||||
server: http://maildev-noauth:1080/
|
||||
@@ -27,10 +27,6 @@ const (
|
||||
colorGrey = "Warning"
|
||||
)
|
||||
|
||||
const (
|
||||
Integration = "msteamsv2"
|
||||
)
|
||||
|
||||
type Notifier struct {
|
||||
conf *config.MSTeamsV2Config
|
||||
titleLink string
|
||||
@@ -91,7 +87,7 @@ type teamsMessage struct {
|
||||
|
||||
// New returns a new notifier that uses the Microsoft Teams Power Platform connector.
|
||||
func New(c *config.MSTeamsV2Config, t *template.Template, titleLink string, l *slog.Logger, httpOpts ...commoncfg.HTTPClientOption) (*Notifier, error) {
|
||||
client, err := notify.NewClientWithTracing(*c.HTTPConfig, Integration, httpOpts...)
|
||||
client, err := commoncfg.NewClientFromConfig(*c.HTTPConfig, "msteamsv2", httpOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
my_secret_api_key
|
||||
|
||||
@@ -1,290 +0,0 @@
|
||||
package opsgenie
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"maps"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
commoncfg "github.com/prometheus/common/config"
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
)
|
||||
|
||||
const (
|
||||
Integration = "opsgenie"
|
||||
)
|
||||
|
||||
// https://docs.opsgenie.com/docs/alert-api - 130 characters meaning runes.
|
||||
const maxMessageLenRunes = 130
|
||||
|
||||
// Notifier implements a Notifier for OpsGenie notifications.
|
||||
type Notifier struct {
|
||||
conf *config.OpsGenieConfig
|
||||
tmpl *template.Template
|
||||
logger *slog.Logger
|
||||
client *http.Client
|
||||
retrier *notify.Retrier
|
||||
}
|
||||
|
||||
// New returns a new OpsGenie notifier.
|
||||
func New(c *config.OpsGenieConfig, t *template.Template, l *slog.Logger, httpOpts ...commoncfg.HTTPClientOption) (*Notifier, error) {
|
||||
client, err := notify.NewClientWithTracing(*c.HTTPConfig, Integration, httpOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Notifier{
|
||||
conf: c,
|
||||
tmpl: t,
|
||||
logger: l,
|
||||
client: client,
|
||||
retrier: ¬ify.Retrier{RetryCodes: []int{http.StatusTooManyRequests}},
|
||||
}, nil
|
||||
}
|
||||
|
||||
type opsGenieCreateMessage struct {
|
||||
Alias string `json:"alias"`
|
||||
Message string `json:"message"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Details map[string]string `json:"details"`
|
||||
Source string `json:"source"`
|
||||
Responders []opsGenieCreateMessageResponder `json:"responders,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
Note string `json:"note,omitempty"`
|
||||
Priority string `json:"priority,omitempty"`
|
||||
Entity string `json:"entity,omitempty"`
|
||||
Actions []string `json:"actions,omitempty"`
|
||||
}
|
||||
|
||||
type opsGenieCreateMessageResponder struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Type string `json:"type"` // team, user, escalation, schedule etc.
|
||||
}
|
||||
|
||||
type opsGenieCloseMessage struct {
|
||||
Source string `json:"source"`
|
||||
}
|
||||
|
||||
type opsGenieUpdateMessageMessage struct {
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
type opsGenieUpdateDescriptionMessage struct {
|
||||
Description string `json:"description,omitempty"`
|
||||
}
|
||||
|
||||
// Notify implements the Notifier interface.
|
||||
func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
|
||||
requests, retry, err := n.createRequests(ctx, as...)
|
||||
if err != nil {
|
||||
return retry, err
|
||||
}
|
||||
|
||||
for _, req := range requests {
|
||||
req.Header.Set("User-Agent", notify.UserAgentHeader)
|
||||
resp, err := n.client.Do(req) //nolint:bodyclose
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
shouldRetry, err := n.retrier.Check(resp.StatusCode, resp.Body)
|
||||
notify.Drain(resp)
|
||||
if err != nil {
|
||||
return shouldRetry, notify.NewErrorWithReason(notify.GetFailureReasonFromStatusCode(resp.StatusCode), err)
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Like Split but filter out empty strings.
|
||||
func safeSplit(s, sep string) []string {
|
||||
a := strings.Split(strings.TrimSpace(s), sep)
|
||||
b := a[:0]
|
||||
for _, x := range a {
|
||||
if x != "" {
|
||||
b = append(b, x)
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Create requests for a list of alerts.
|
||||
func (n *Notifier) createRequests(ctx context.Context, as ...*types.Alert) ([]*http.Request, bool, error) {
|
||||
key, err := notify.ExtractGroupKey(ctx)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
logger := n.logger.With("group_key", key)
|
||||
logger.DebugContext(ctx, "extracted group key")
|
||||
|
||||
data := notify.GetTemplateData(ctx, n.tmpl, as, logger)
|
||||
|
||||
tmpl := notify.TmplText(n.tmpl, data, &err)
|
||||
|
||||
details := make(map[string]string)
|
||||
|
||||
maps.Copy(details, data.CommonLabels)
|
||||
|
||||
for k, v := range n.conf.Details {
|
||||
details[k] = tmpl(v)
|
||||
}
|
||||
|
||||
requests := []*http.Request{}
|
||||
|
||||
var (
|
||||
alias = key.Hash()
|
||||
alerts = types.Alerts(as...)
|
||||
)
|
||||
switch alerts.Status() {
|
||||
case model.AlertResolved:
|
||||
resolvedEndpointURL := n.conf.APIURL.Copy()
|
||||
resolvedEndpointURL.Path += fmt.Sprintf("v2/alerts/%s/close", alias)
|
||||
q := resolvedEndpointURL.Query()
|
||||
q.Set("identifierType", "alias")
|
||||
resolvedEndpointURL.RawQuery = q.Encode()
|
||||
msg := &opsGenieCloseMessage{Source: tmpl(n.conf.Source)}
|
||||
var buf bytes.Buffer
|
||||
if err := json.NewEncoder(&buf).Encode(msg); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
req, err := http.NewRequest("POST", resolvedEndpointURL.String(), &buf)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
requests = append(requests, req.WithContext(ctx))
|
||||
default:
|
||||
message, truncated := notify.TruncateInRunes(tmpl(n.conf.Message), maxMessageLenRunes)
|
||||
if truncated {
|
||||
logger.WarnContext(ctx, "Truncated message", "alert", key, "max_runes", maxMessageLenRunes)
|
||||
}
|
||||
|
||||
createEndpointURL := n.conf.APIURL.Copy()
|
||||
createEndpointURL.Path += "v2/alerts"
|
||||
|
||||
var responders []opsGenieCreateMessageResponder
|
||||
for _, r := range n.conf.Responders {
|
||||
responder := opsGenieCreateMessageResponder{
|
||||
ID: tmpl(r.ID),
|
||||
Name: tmpl(r.Name),
|
||||
Username: tmpl(r.Username),
|
||||
Type: tmpl(r.Type),
|
||||
}
|
||||
|
||||
if responder == (opsGenieCreateMessageResponder{}) {
|
||||
// Filter out empty responders. This is useful if you want to fill
|
||||
// responders dynamically from alert's common labels.
|
||||
continue
|
||||
}
|
||||
|
||||
if responder.Type == "teams" {
|
||||
teams := safeSplit(responder.Name, ",")
|
||||
for _, team := range teams {
|
||||
newResponder := opsGenieCreateMessageResponder{
|
||||
Name: tmpl(team),
|
||||
Type: tmpl("team"),
|
||||
}
|
||||
responders = append(responders, newResponder)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
responders = append(responders, responder)
|
||||
}
|
||||
|
||||
msg := &opsGenieCreateMessage{
|
||||
Alias: alias,
|
||||
Message: message,
|
||||
Description: tmpl(n.conf.Description),
|
||||
Details: details,
|
||||
Source: tmpl(n.conf.Source),
|
||||
Responders: responders,
|
||||
Tags: safeSplit(tmpl(n.conf.Tags), ","),
|
||||
Note: tmpl(n.conf.Note),
|
||||
Priority: tmpl(n.conf.Priority),
|
||||
Entity: tmpl(n.conf.Entity),
|
||||
Actions: safeSplit(tmpl(n.conf.Actions), ","),
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := json.NewEncoder(&buf).Encode(msg); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
req, err := http.NewRequest("POST", createEndpointURL.String(), &buf)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
requests = append(requests, req.WithContext(ctx))
|
||||
|
||||
if n.conf.UpdateAlerts {
|
||||
updateMessageEndpointURL := n.conf.APIURL.Copy()
|
||||
updateMessageEndpointURL.Path += fmt.Sprintf("v2/alerts/%s/message", alias)
|
||||
q := updateMessageEndpointURL.Query()
|
||||
q.Set("identifierType", "alias")
|
||||
updateMessageEndpointURL.RawQuery = q.Encode()
|
||||
updateMsgMsg := &opsGenieUpdateMessageMessage{
|
||||
Message: msg.Message,
|
||||
}
|
||||
var updateMessageBuf bytes.Buffer
|
||||
if err := json.NewEncoder(&updateMessageBuf).Encode(updateMsgMsg); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
req, err := http.NewRequest("PUT", updateMessageEndpointURL.String(), &updateMessageBuf)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
requests = append(requests, req)
|
||||
|
||||
updateDescriptionEndpointURL := n.conf.APIURL.Copy()
|
||||
updateDescriptionEndpointURL.Path += fmt.Sprintf("v2/alerts/%s/description", alias)
|
||||
q = updateDescriptionEndpointURL.Query()
|
||||
q.Set("identifierType", "alias")
|
||||
updateDescriptionEndpointURL.RawQuery = q.Encode()
|
||||
updateDescMsg := &opsGenieUpdateDescriptionMessage{
|
||||
Description: msg.Description,
|
||||
}
|
||||
|
||||
var updateDescriptionBuf bytes.Buffer
|
||||
if err := json.NewEncoder(&updateDescriptionBuf).Encode(updateDescMsg); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
req, err = http.NewRequest("PUT", updateDescriptionEndpointURL.String(), &updateDescriptionBuf)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
requests = append(requests, req.WithContext(ctx))
|
||||
}
|
||||
}
|
||||
|
||||
var apiKey string
|
||||
if n.conf.APIKey != "" {
|
||||
apiKey = tmpl(string(n.conf.APIKey))
|
||||
} else {
|
||||
content, err := os.ReadFile(n.conf.APIKeyFile)
|
||||
if err != nil {
|
||||
return nil, false, errors.WrapInternalf(err, errors.CodeInternal, "read key_file error")
|
||||
}
|
||||
apiKey = tmpl(string(content))
|
||||
apiKey = strings.TrimSpace(string(apiKey))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, false, errors.WrapInternalf(err, errors.CodeInternal, "templating error")
|
||||
}
|
||||
|
||||
for _, req := range requests {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", fmt.Sprintf("GenieKey %s", apiKey))
|
||||
}
|
||||
|
||||
return requests, true, nil
|
||||
}
|
||||
@@ -1,333 +0,0 @@
|
||||
package opsgenie
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
commoncfg "github.com/prometheus/common/config"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/prometheus/common/promslog"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/notify/test"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
)
|
||||
|
||||
func TestOpsGenieRetry(t *testing.T) {
|
||||
notifier, err := New(
|
||||
&config.OpsGenieConfig{
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
},
|
||||
test.CreateTmpl(t),
|
||||
promslog.NewNopLogger(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
retryCodes := append(test.DefaultRetryCodes(), http.StatusTooManyRequests)
|
||||
for statusCode, expected := range test.RetryTests(retryCodes) {
|
||||
actual, _ := notifier.retrier.Check(statusCode, nil)
|
||||
require.Equal(t, expected, actual, "error on status %d", statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpsGenieRedactedURL(t *testing.T) {
|
||||
ctx, u, fn := test.GetContextWithCancelingURL()
|
||||
defer fn()
|
||||
|
||||
key := "key"
|
||||
notifier, err := New(
|
||||
&config.OpsGenieConfig{
|
||||
APIURL: &config.URL{URL: u},
|
||||
APIKey: config.Secret(key),
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
},
|
||||
test.CreateTmpl(t),
|
||||
promslog.NewNopLogger(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
test.AssertNotifyLeaksNoSecret(ctx, t, notifier, key)
|
||||
}
|
||||
|
||||
func TestGettingOpsGegineApikeyFromFile(t *testing.T) {
|
||||
ctx, u, fn := test.GetContextWithCancelingURL()
|
||||
defer fn()
|
||||
|
||||
key := "key"
|
||||
|
||||
f, err := os.CreateTemp(t.TempDir(), "opsgenie_test")
|
||||
require.NoError(t, err, "creating temp file failed")
|
||||
_, err = f.WriteString(key)
|
||||
require.NoError(t, err, "writing to temp file failed")
|
||||
|
||||
notifier, err := New(
|
||||
&config.OpsGenieConfig{
|
||||
APIURL: &config.URL{URL: u},
|
||||
APIKeyFile: f.Name(),
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
},
|
||||
test.CreateTmpl(t),
|
||||
promslog.NewNopLogger(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
test.AssertNotifyLeaksNoSecret(ctx, t, notifier, key)
|
||||
}
|
||||
|
||||
func TestOpsGenie(t *testing.T) {
|
||||
u, err := url.Parse("https://opsgenie/api")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse URL: %v", err)
|
||||
}
|
||||
logger := promslog.NewNopLogger()
|
||||
tmpl := test.CreateTmpl(t)
|
||||
|
||||
for _, tc := range []struct {
|
||||
title string
|
||||
cfg *config.OpsGenieConfig
|
||||
|
||||
expectedEmptyAlertBody string
|
||||
expectedBody string
|
||||
}{
|
||||
{
|
||||
title: "config without details",
|
||||
cfg: &config.OpsGenieConfig{
|
||||
NotifierConfig: config.NotifierConfig{
|
||||
VSendResolved: true,
|
||||
},
|
||||
Message: `{{ .CommonLabels.Message }}`,
|
||||
Description: `{{ .CommonLabels.Description }}`,
|
||||
Source: `{{ .CommonLabels.Source }}`,
|
||||
Responders: []config.OpsGenieConfigResponder{
|
||||
{
|
||||
Name: `{{ .CommonLabels.ResponderName1 }}`,
|
||||
Type: `{{ .CommonLabels.ResponderType1 }}`,
|
||||
},
|
||||
{
|
||||
Name: `{{ .CommonLabels.ResponderName2 }}`,
|
||||
Type: `{{ .CommonLabels.ResponderType2 }}`,
|
||||
},
|
||||
},
|
||||
Tags: `{{ .CommonLabels.Tags }}`,
|
||||
Note: `{{ .CommonLabels.Note }}`,
|
||||
Priority: `{{ .CommonLabels.Priority }}`,
|
||||
Entity: `{{ .CommonLabels.Entity }}`,
|
||||
Actions: `{{ .CommonLabels.Actions }}`,
|
||||
APIKey: `{{ .ExternalURL }}`,
|
||||
APIURL: &config.URL{URL: u},
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
},
|
||||
expectedEmptyAlertBody: `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"","details":{},"source":""}
|
||||
`,
|
||||
expectedBody: `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"message","description":"description","details":{"Actions":"doThis,doThat","Description":"description","Entity":"test-domain","Message":"message","Note":"this is a note","Priority":"P1","ResponderName1":"TeamA","ResponderName2":"EscalationA","ResponderName3":"TeamA,TeamB","ResponderType1":"team","ResponderType2":"escalation","ResponderType3":"teams","Source":"http://prometheus","Tags":"tag1,tag2"},"source":"http://prometheus","responders":[{"name":"TeamA","type":"team"},{"name":"EscalationA","type":"escalation"}],"tags":["tag1","tag2"],"note":"this is a note","priority":"P1","entity":"test-domain","actions":["doThis","doThat"]}
|
||||
`,
|
||||
},
|
||||
{
|
||||
title: "config with details",
|
||||
cfg: &config.OpsGenieConfig{
|
||||
NotifierConfig: config.NotifierConfig{
|
||||
VSendResolved: true,
|
||||
},
|
||||
Message: `{{ .CommonLabels.Message }}`,
|
||||
Description: `{{ .CommonLabels.Description }}`,
|
||||
Source: `{{ .CommonLabels.Source }}`,
|
||||
Details: map[string]string{
|
||||
"Description": `adjusted {{ .CommonLabels.Description }}`,
|
||||
},
|
||||
Responders: []config.OpsGenieConfigResponder{
|
||||
{
|
||||
Name: `{{ .CommonLabels.ResponderName1 }}`,
|
||||
Type: `{{ .CommonLabels.ResponderType1 }}`,
|
||||
},
|
||||
{
|
||||
Name: `{{ .CommonLabels.ResponderName2 }}`,
|
||||
Type: `{{ .CommonLabels.ResponderType2 }}`,
|
||||
},
|
||||
},
|
||||
Tags: `{{ .CommonLabels.Tags }}`,
|
||||
Note: `{{ .CommonLabels.Note }}`,
|
||||
Priority: `{{ .CommonLabels.Priority }}`,
|
||||
Entity: `{{ .CommonLabels.Entity }}`,
|
||||
Actions: `{{ .CommonLabels.Actions }}`,
|
||||
APIKey: `{{ .ExternalURL }}`,
|
||||
APIURL: &config.URL{URL: u},
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
},
|
||||
expectedEmptyAlertBody: `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"","details":{"Description":"adjusted "},"source":""}
|
||||
`,
|
||||
expectedBody: `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"message","description":"description","details":{"Actions":"doThis,doThat","Description":"adjusted description","Entity":"test-domain","Message":"message","Note":"this is a note","Priority":"P1","ResponderName1":"TeamA","ResponderName2":"EscalationA","ResponderName3":"TeamA,TeamB","ResponderType1":"team","ResponderType2":"escalation","ResponderType3":"teams","Source":"http://prometheus","Tags":"tag1,tag2"},"source":"http://prometheus","responders":[{"name":"TeamA","type":"team"},{"name":"EscalationA","type":"escalation"}],"tags":["tag1","tag2"],"note":"this is a note","priority":"P1","entity":"test-domain","actions":["doThis","doThat"]}
|
||||
`,
|
||||
},
|
||||
{
|
||||
title: "config with multiple teams",
|
||||
cfg: &config.OpsGenieConfig{
|
||||
NotifierConfig: config.NotifierConfig{
|
||||
VSendResolved: true,
|
||||
},
|
||||
Message: `{{ .CommonLabels.Message }}`,
|
||||
Description: `{{ .CommonLabels.Description }}`,
|
||||
Source: `{{ .CommonLabels.Source }}`,
|
||||
Details: map[string]string{
|
||||
"Description": `adjusted {{ .CommonLabels.Description }}`,
|
||||
},
|
||||
Responders: []config.OpsGenieConfigResponder{
|
||||
{
|
||||
Name: `{{ .CommonLabels.ResponderName3 }}`,
|
||||
Type: `{{ .CommonLabels.ResponderType3 }}`,
|
||||
},
|
||||
},
|
||||
Tags: `{{ .CommonLabels.Tags }}`,
|
||||
Note: `{{ .CommonLabels.Note }}`,
|
||||
Priority: `{{ .CommonLabels.Priority }}`,
|
||||
APIKey: `{{ .ExternalURL }}`,
|
||||
APIURL: &config.URL{URL: u},
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
},
|
||||
expectedEmptyAlertBody: `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"","details":{"Description":"adjusted "},"source":""}
|
||||
`,
|
||||
expectedBody: `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"message","description":"description","details":{"Actions":"doThis,doThat","Description":"adjusted description","Entity":"test-domain","Message":"message","Note":"this is a note","Priority":"P1","ResponderName1":"TeamA","ResponderName2":"EscalationA","ResponderName3":"TeamA,TeamB","ResponderType1":"team","ResponderType2":"escalation","ResponderType3":"teams","Source":"http://prometheus","Tags":"tag1,tag2"},"source":"http://prometheus","responders":[{"name":"TeamA","type":"team"},{"name":"TeamB","type":"team"}],"tags":["tag1","tag2"],"note":"this is a note","priority":"P1"}
|
||||
`,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.title, func(t *testing.T) {
|
||||
notifier, err := New(tc.cfg, tmpl, logger)
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = notify.WithGroupKey(ctx, "1")
|
||||
|
||||
expectedURL, _ := url.Parse("https://opsgenie/apiv2/alerts")
|
||||
|
||||
// Empty alert.
|
||||
alert1 := &types.Alert{
|
||||
Alert: model.Alert{
|
||||
StartsAt: time.Now(),
|
||||
EndsAt: time.Now().Add(time.Hour),
|
||||
},
|
||||
}
|
||||
|
||||
req, retry, err := notifier.createRequests(ctx, alert1)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, req, 1)
|
||||
require.True(t, retry)
|
||||
require.Equal(t, expectedURL, req[0].URL)
|
||||
require.Equal(t, "GenieKey http://am", req[0].Header.Get("Authorization"))
|
||||
require.Equal(t, tc.expectedEmptyAlertBody, readBody(t, req[0]))
|
||||
|
||||
// Fully defined alert.
|
||||
alert2 := &types.Alert{
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{
|
||||
"Message": "message",
|
||||
"Description": "description",
|
||||
"Source": "http://prometheus",
|
||||
"ResponderName1": "TeamA",
|
||||
"ResponderType1": "team",
|
||||
"ResponderName2": "EscalationA",
|
||||
"ResponderType2": "escalation",
|
||||
"ResponderName3": "TeamA,TeamB",
|
||||
"ResponderType3": "teams",
|
||||
"Tags": "tag1,tag2",
|
||||
"Note": "this is a note",
|
||||
"Priority": "P1",
|
||||
"Entity": "test-domain",
|
||||
"Actions": "doThis,doThat",
|
||||
},
|
||||
StartsAt: time.Now(),
|
||||
EndsAt: time.Now().Add(time.Hour),
|
||||
},
|
||||
}
|
||||
req, retry, err = notifier.createRequests(ctx, alert2)
|
||||
require.NoError(t, err)
|
||||
require.True(t, retry)
|
||||
require.Len(t, req, 1)
|
||||
require.Equal(t, tc.expectedBody, readBody(t, req[0]))
|
||||
|
||||
// Broken API Key Template.
|
||||
tc.cfg.APIKey = "{{ kaput "
|
||||
_, _, err = notifier.createRequests(ctx, alert2)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, "template: :1: function \"kaput\" not defined", err.Error())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpsGenieWithUpdate(t *testing.T) {
|
||||
u, err := url.Parse("https://test-opsgenie-url")
|
||||
require.NoError(t, err)
|
||||
tmpl := test.CreateTmpl(t)
|
||||
ctx := context.Background()
|
||||
ctx = notify.WithGroupKey(ctx, "1")
|
||||
opsGenieConfigWithUpdate := config.OpsGenieConfig{
|
||||
Message: `{{ .CommonLabels.Message }}`,
|
||||
Description: `{{ .CommonLabels.Description }}`,
|
||||
UpdateAlerts: true,
|
||||
APIKey: "test-api-key",
|
||||
APIURL: &config.URL{URL: u},
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
}
|
||||
notifierWithUpdate, err := New(&opsGenieConfigWithUpdate, tmpl, promslog.NewNopLogger())
|
||||
alert := &types.Alert{
|
||||
Alert: model.Alert{
|
||||
StartsAt: time.Now(),
|
||||
EndsAt: time.Now().Add(time.Hour),
|
||||
Labels: model.LabelSet{
|
||||
"Message": "new message",
|
||||
"Description": "new description",
|
||||
},
|
||||
},
|
||||
}
|
||||
require.NoError(t, err)
|
||||
requests, retry, err := notifierWithUpdate.createRequests(ctx, alert)
|
||||
require.NoError(t, err)
|
||||
require.True(t, retry)
|
||||
require.Len(t, requests, 3)
|
||||
|
||||
body0 := readBody(t, requests[0])
|
||||
body1 := readBody(t, requests[1])
|
||||
body2 := readBody(t, requests[2])
|
||||
key, _ := notify.ExtractGroupKey(ctx)
|
||||
alias := key.Hash()
|
||||
|
||||
require.Equal(t, "https://test-opsgenie-url/v2/alerts", requests[0].URL.String())
|
||||
require.NotEmpty(t, body0)
|
||||
|
||||
require.Equal(t, requests[1].URL.String(), fmt.Sprintf("https://test-opsgenie-url/v2/alerts/%s/message?identifierType=alias", alias))
|
||||
require.JSONEq(t, `{"message":"new message"}`, body1)
|
||||
require.Equal(t, requests[2].URL.String(), fmt.Sprintf("https://test-opsgenie-url/v2/alerts/%s/description?identifierType=alias", alias))
|
||||
require.JSONEq(t, `{"description":"new description"}`, body2)
|
||||
}
|
||||
|
||||
func TestOpsGenieApiKeyFile(t *testing.T) {
|
||||
u, err := url.Parse("https://test-opsgenie-url")
|
||||
require.NoError(t, err)
|
||||
tmpl := test.CreateTmpl(t)
|
||||
ctx := context.Background()
|
||||
ctx = notify.WithGroupKey(ctx, "1")
|
||||
opsGenieConfigWithUpdate := config.OpsGenieConfig{
|
||||
APIKeyFile: `./api_key_file`,
|
||||
APIURL: &config.URL{URL: u},
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
}
|
||||
notifierWithUpdate, err := New(&opsGenieConfigWithUpdate, tmpl, promslog.NewNopLogger())
|
||||
|
||||
require.NoError(t, err)
|
||||
requests, _, err := notifierWithUpdate.createRequests(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "GenieKey my_secret_api_key", requests[0].Header.Get("Authorization"))
|
||||
}
|
||||
|
||||
func readBody(t *testing.T, r *http.Request) string {
|
||||
t.Helper()
|
||||
body, err := io.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
return string(body)
|
||||
}
|
||||
@@ -1,374 +0,0 @@
|
||||
package pagerduty
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/alecthomas/units"
|
||||
commoncfg "github.com/prometheus/common/config"
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
)
|
||||
|
||||
const (
|
||||
Integration = "pagerduty"
|
||||
)
|
||||
|
||||
const (
|
||||
maxEventSize int = 512000
|
||||
// https://developer.pagerduty.com/docs/ZG9jOjExMDI5NTc4-send-a-v1-event - 1024 characters or runes.
|
||||
maxV1DescriptionLenRunes = 1024
|
||||
// https://developer.pagerduty.com/docs/ZG9jOjExMDI5NTgx-send-an-alert-event - 1024 characters or runes.
|
||||
maxV2SummaryLenRunes = 1024
|
||||
)
|
||||
|
||||
// Notifier implements a Notifier for PagerDuty notifications.
|
||||
type Notifier struct {
|
||||
conf *config.PagerdutyConfig
|
||||
tmpl *template.Template
|
||||
logger *slog.Logger
|
||||
apiV1 string // for tests.
|
||||
client *http.Client
|
||||
retrier *notify.Retrier
|
||||
}
|
||||
|
||||
// New returns a new PagerDuty notifier.
|
||||
func New(c *config.PagerdutyConfig, t *template.Template, l *slog.Logger, httpOpts ...commoncfg.HTTPClientOption) (*Notifier, error) {
|
||||
client, err := notify.NewClientWithTracing(*c.HTTPConfig, Integration, httpOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n := &Notifier{conf: c, tmpl: t, logger: l, client: client}
|
||||
if c.ServiceKey != "" || c.ServiceKeyFile != "" {
|
||||
n.apiV1 = "https://events.pagerduty.com/generic/2010-04-15/create_event.json"
|
||||
// Retrying can solve the issue on 403 (rate limiting) and 5xx response codes.
|
||||
// https://developer.pagerduty.com/docs/events-api-v1-overview#api-response-codes--retry-logic
|
||||
n.retrier = ¬ify.Retrier{RetryCodes: []int{http.StatusForbidden}, CustomDetailsFunc: errDetails}
|
||||
} else {
|
||||
// Retrying can solve the issue on 429 (rate limiting) and 5xx response codes.
|
||||
// https://developer.pagerduty.com/docs/events-api-v2-overview#response-codes--retry-logic
|
||||
n.retrier = ¬ify.Retrier{RetryCodes: []int{http.StatusTooManyRequests}, CustomDetailsFunc: errDetails}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
const (
|
||||
pagerDutyEventTrigger = "trigger"
|
||||
pagerDutyEventResolve = "resolve"
|
||||
)
|
||||
|
||||
type pagerDutyMessage struct {
|
||||
RoutingKey string `json:"routing_key,omitempty"`
|
||||
ServiceKey string `json:"service_key,omitempty"`
|
||||
DedupKey string `json:"dedup_key,omitempty"`
|
||||
IncidentKey string `json:"incident_key,omitempty"`
|
||||
EventType string `json:"event_type,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
EventAction string `json:"event_action"`
|
||||
Payload *pagerDutyPayload `json:"payload"`
|
||||
Client string `json:"client,omitempty"`
|
||||
ClientURL string `json:"client_url,omitempty"`
|
||||
Details map[string]any `json:"details,omitempty"`
|
||||
Images []pagerDutyImage `json:"images,omitempty"`
|
||||
Links []pagerDutyLink `json:"links,omitempty"`
|
||||
}
|
||||
|
||||
type pagerDutyLink struct {
|
||||
HRef string `json:"href"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
type pagerDutyImage struct {
|
||||
Src string `json:"src"`
|
||||
Alt string `json:"alt"`
|
||||
Href string `json:"href"`
|
||||
}
|
||||
|
||||
type pagerDutyPayload struct {
|
||||
Summary string `json:"summary"`
|
||||
Source string `json:"source"`
|
||||
Severity string `json:"severity"`
|
||||
Timestamp string `json:"timestamp,omitempty"`
|
||||
Class string `json:"class,omitempty"`
|
||||
Component string `json:"component,omitempty"`
|
||||
Group string `json:"group,omitempty"`
|
||||
CustomDetails map[string]any `json:"custom_details,omitempty"`
|
||||
}
|
||||
|
||||
func (n *Notifier) encodeMessage(ctx context.Context, msg *pagerDutyMessage) (bytes.Buffer, error) {
|
||||
var buf bytes.Buffer
|
||||
if err := json.NewEncoder(&buf).Encode(msg); err != nil {
|
||||
return buf, errors.WrapInternalf(err, errors.CodeInternal, "failed to encode PagerDuty message")
|
||||
}
|
||||
|
||||
if buf.Len() > maxEventSize {
|
||||
truncatedMsg := fmt.Sprintf("Custom details have been removed because the original event exceeds the maximum size of %s", units.MetricBytes(maxEventSize).String())
|
||||
|
||||
if n.apiV1 != "" {
|
||||
msg.Details = map[string]any{"error": truncatedMsg}
|
||||
} else {
|
||||
msg.Payload.CustomDetails = map[string]any{"error": truncatedMsg}
|
||||
}
|
||||
|
||||
n.logger.WarnContext(ctx, "Truncated Details because message of size exceeds limit", "message_size", units.MetricBytes(buf.Len()).String(), "max_size", units.MetricBytes(maxEventSize).String())
|
||||
|
||||
buf.Reset()
|
||||
if err := json.NewEncoder(&buf).Encode(msg); err != nil {
|
||||
return buf, errors.WrapInternalf(err, errors.CodeInternal, "failed to encode PagerDuty message")
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (n *Notifier) notifyV1(
|
||||
ctx context.Context,
|
||||
eventType string,
|
||||
key notify.Key,
|
||||
data *template.Data,
|
||||
details map[string]any,
|
||||
) (bool, error) {
|
||||
var tmplErr error
|
||||
tmpl := notify.TmplText(n.tmpl, data, &tmplErr)
|
||||
|
||||
description, truncated := notify.TruncateInRunes(tmpl(n.conf.Description), maxV1DescriptionLenRunes)
|
||||
if truncated {
|
||||
n.logger.WarnContext(ctx, "Truncated description", "key", key, "max_runes", maxV1DescriptionLenRunes)
|
||||
}
|
||||
|
||||
serviceKey := string(n.conf.ServiceKey)
|
||||
if serviceKey == "" {
|
||||
content, fileErr := os.ReadFile(n.conf.ServiceKeyFile)
|
||||
if fileErr != nil {
|
||||
return false, errors.WrapInternalf(fileErr, errors.CodeInternal, "failed to read service key from file")
|
||||
}
|
||||
serviceKey = strings.TrimSpace(string(content))
|
||||
}
|
||||
|
||||
msg := &pagerDutyMessage{
|
||||
ServiceKey: tmpl(serviceKey),
|
||||
EventType: eventType,
|
||||
IncidentKey: key.Hash(),
|
||||
Description: description,
|
||||
Details: details,
|
||||
}
|
||||
|
||||
if eventType == pagerDutyEventTrigger {
|
||||
msg.Client = tmpl(n.conf.Client)
|
||||
msg.ClientURL = tmpl(n.conf.ClientURL)
|
||||
}
|
||||
|
||||
if tmplErr != nil {
|
||||
return false, errors.WrapInternalf(tmplErr, errors.CodeInternal, "failed to template PagerDuty v1 message")
|
||||
}
|
||||
|
||||
// Ensure that the service key isn't empty after templating.
|
||||
if msg.ServiceKey == "" {
|
||||
return false, errors.NewInternalf(errors.CodeInternal, "service key cannot be empty")
|
||||
}
|
||||
|
||||
encodedMsg, err := n.encodeMessage(ctx, msg)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
resp, err := notify.PostJSON(ctx, n.client, n.apiV1, &encodedMsg) //nolint:bodyclose
|
||||
if err != nil {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "failed to post message to PagerDuty v1")
|
||||
}
|
||||
defer notify.Drain(resp)
|
||||
|
||||
return n.retrier.Check(resp.StatusCode, resp.Body)
|
||||
}
|
||||
|
||||
func (n *Notifier) notifyV2(
|
||||
ctx context.Context,
|
||||
eventType string,
|
||||
key notify.Key,
|
||||
data *template.Data,
|
||||
details map[string]any,
|
||||
) (bool, error) {
|
||||
var tmplErr error
|
||||
tmpl := notify.TmplText(n.tmpl, data, &tmplErr)
|
||||
|
||||
if n.conf.Severity == "" {
|
||||
n.conf.Severity = "error"
|
||||
}
|
||||
|
||||
summary, truncated := notify.TruncateInRunes(tmpl(n.conf.Description), maxV2SummaryLenRunes)
|
||||
if truncated {
|
||||
n.logger.WarnContext(ctx, "Truncated summary", "key", key, "max_runes", maxV2SummaryLenRunes)
|
||||
}
|
||||
|
||||
routingKey := string(n.conf.RoutingKey)
|
||||
if routingKey == "" {
|
||||
content, fileErr := os.ReadFile(n.conf.RoutingKeyFile)
|
||||
if fileErr != nil {
|
||||
return false, errors.WrapInternalf(fileErr, errors.CodeInternal, "failed to read routing key from file")
|
||||
}
|
||||
routingKey = strings.TrimSpace(string(content))
|
||||
}
|
||||
|
||||
msg := &pagerDutyMessage{
|
||||
Client: tmpl(n.conf.Client),
|
||||
ClientURL: tmpl(n.conf.ClientURL),
|
||||
RoutingKey: tmpl(routingKey),
|
||||
EventAction: eventType,
|
||||
DedupKey: key.Hash(),
|
||||
Images: make([]pagerDutyImage, 0, len(n.conf.Images)),
|
||||
Links: make([]pagerDutyLink, 0, len(n.conf.Links)),
|
||||
Payload: &pagerDutyPayload{
|
||||
Summary: summary,
|
||||
Source: tmpl(n.conf.Source),
|
||||
Severity: tmpl(n.conf.Severity),
|
||||
CustomDetails: details,
|
||||
Class: tmpl(n.conf.Class),
|
||||
Component: tmpl(n.conf.Component),
|
||||
Group: tmpl(n.conf.Group),
|
||||
},
|
||||
}
|
||||
|
||||
for _, item := range n.conf.Images {
|
||||
image := pagerDutyImage{
|
||||
Src: tmpl(item.Src),
|
||||
Alt: tmpl(item.Alt),
|
||||
Href: tmpl(item.Href),
|
||||
}
|
||||
|
||||
if image.Src != "" {
|
||||
msg.Images = append(msg.Images, image)
|
||||
}
|
||||
}
|
||||
|
||||
for _, item := range n.conf.Links {
|
||||
link := pagerDutyLink{
|
||||
HRef: tmpl(item.Href),
|
||||
Text: tmpl(item.Text),
|
||||
}
|
||||
|
||||
if link.HRef != "" {
|
||||
msg.Links = append(msg.Links, link)
|
||||
}
|
||||
}
|
||||
|
||||
if tmplErr != nil {
|
||||
return false, errors.WrapInternalf(tmplErr, errors.CodeInternal, "failed to template PagerDuty v2 message")
|
||||
}
|
||||
|
||||
// Ensure that the routing key isn't empty after templating.
|
||||
if msg.RoutingKey == "" {
|
||||
return false, errors.NewInternalf(errors.CodeInternal, "routing key cannot be empty")
|
||||
}
|
||||
|
||||
encodedMsg, err := n.encodeMessage(ctx, msg)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
resp, err := notify.PostJSON(ctx, n.client, n.conf.URL.String(), &encodedMsg) //nolint:bodyclose
|
||||
if err != nil {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "failed to post message to PagerDuty")
|
||||
}
|
||||
defer notify.Drain(resp)
|
||||
|
||||
retry, err := n.retrier.Check(resp.StatusCode, resp.Body)
|
||||
if err != nil {
|
||||
return retry, notify.NewErrorWithReason(notify.GetFailureReasonFromStatusCode(resp.StatusCode), err)
|
||||
}
|
||||
return retry, err
|
||||
}
|
||||
|
||||
// Notify implements the Notifier interface.
|
||||
func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
|
||||
key, err := notify.ExtractGroupKey(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
logger := n.logger.With("group_key", key)
|
||||
|
||||
var (
|
||||
alerts = types.Alerts(as...)
|
||||
data = notify.GetTemplateData(ctx, n.tmpl, as, logger)
|
||||
eventType = pagerDutyEventTrigger
|
||||
)
|
||||
|
||||
if alerts.Status() == model.AlertResolved {
|
||||
eventType = pagerDutyEventResolve
|
||||
}
|
||||
|
||||
logger.DebugContext(ctx, "extracted group key", "event_type", eventType)
|
||||
|
||||
details, err := n.renderDetails(data)
|
||||
if err != nil {
|
||||
return false, errors.WrapInternalf(err, errors.CodeInternal, "failed to render details: %v", err)
|
||||
}
|
||||
|
||||
if n.conf.Timeout > 0 {
|
||||
nfCtx, cancel := context.WithTimeoutCause(ctx, n.conf.Timeout, errors.NewInternalf(errors.CodeTimeout, "configured pagerduty timeout reached (%s)", n.conf.Timeout))
|
||||
defer cancel()
|
||||
ctx = nfCtx
|
||||
}
|
||||
|
||||
nf := n.notifyV2
|
||||
if n.apiV1 != "" {
|
||||
nf = n.notifyV1
|
||||
}
|
||||
retry, err := nf(ctx, eventType, key, data, details)
|
||||
if err != nil {
|
||||
if ctx.Err() != nil {
|
||||
err = errors.WrapInternalf(err, errors.CodeInternal, "failed to notify PagerDuty: %v", context.Cause(ctx))
|
||||
}
|
||||
return retry, err
|
||||
}
|
||||
return retry, nil
|
||||
}
|
||||
|
||||
func errDetails(status int, body io.Reader) string {
|
||||
// See https://v2.developer.pagerduty.com/docs/trigger-events for the v1 events API.
|
||||
// See https://v2.developer.pagerduty.com/docs/send-an-event-events-api-v2 for the v2 events API.
|
||||
if status != http.StatusBadRequest || body == nil {
|
||||
return ""
|
||||
}
|
||||
var pgr struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Errors []string `json:"errors"`
|
||||
}
|
||||
if err := json.NewDecoder(body).Decode(&pgr); err != nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%s: %s", pgr.Message, strings.Join(pgr.Errors, ","))
|
||||
}
|
||||
|
||||
func (n *Notifier) renderDetails(
|
||||
data *template.Data,
|
||||
) (map[string]any, error) {
|
||||
var (
|
||||
tmplTextErr error
|
||||
tmplText = notify.TmplText(n.tmpl, data, &tmplTextErr)
|
||||
tmplTextFunc = func(tmpl string) (string, error) {
|
||||
return tmplText(tmpl), tmplTextErr
|
||||
}
|
||||
)
|
||||
var err error
|
||||
rendered := make(map[string]any, len(n.conf.Details))
|
||||
for k, v := range n.conf.Details {
|
||||
rendered[k], err = template.DeepCopyWithTemplate(v, tmplTextFunc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return rendered, nil
|
||||
}
|
||||
@@ -1,879 +0,0 @@
|
||||
package pagerduty
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
commoncfg "github.com/prometheus/common/config"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/prometheus/common/promslog"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/notify/test"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
)
|
||||
|
||||
func TestPagerDutyRetryV1(t *testing.T) {
|
||||
notifier, err := New(
|
||||
&config.PagerdutyConfig{
|
||||
ServiceKey: config.Secret("01234567890123456789012345678901"),
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
},
|
||||
test.CreateTmpl(t),
|
||||
promslog.NewNopLogger(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
retryCodes := append(test.DefaultRetryCodes(), http.StatusForbidden)
|
||||
for statusCode, expected := range test.RetryTests(retryCodes) {
|
||||
actual, _ := notifier.retrier.Check(statusCode, nil)
|
||||
require.Equal(t, expected, actual, "retryv1 - error on status %d", statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPagerDutyRetryV2(t *testing.T) {
|
||||
notifier, err := New(
|
||||
&config.PagerdutyConfig{
|
||||
RoutingKey: config.Secret("01234567890123456789012345678901"),
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
},
|
||||
test.CreateTmpl(t),
|
||||
promslog.NewNopLogger(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
retryCodes := append(test.DefaultRetryCodes(), http.StatusTooManyRequests)
|
||||
for statusCode, expected := range test.RetryTests(retryCodes) {
|
||||
actual, _ := notifier.retrier.Check(statusCode, nil)
|
||||
require.Equal(t, expected, actual, "retryv2 - error on status %d", statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPagerDutyRedactedURLV1(t *testing.T) {
|
||||
ctx, u, fn := test.GetContextWithCancelingURL()
|
||||
defer fn()
|
||||
|
||||
key := "01234567890123456789012345678901"
|
||||
notifier, err := New(
|
||||
&config.PagerdutyConfig{
|
||||
ServiceKey: config.Secret(key),
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
},
|
||||
test.CreateTmpl(t),
|
||||
promslog.NewNopLogger(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
notifier.apiV1 = u.String()
|
||||
|
||||
test.AssertNotifyLeaksNoSecret(ctx, t, notifier, key)
|
||||
}
|
||||
|
||||
func TestPagerDutyRedactedURLV2(t *testing.T) {
|
||||
ctx, u, fn := test.GetContextWithCancelingURL()
|
||||
defer fn()
|
||||
|
||||
key := "01234567890123456789012345678901"
|
||||
notifier, err := New(
|
||||
&config.PagerdutyConfig{
|
||||
URL: &config.URL{URL: u},
|
||||
RoutingKey: config.Secret(key),
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
},
|
||||
test.CreateTmpl(t),
|
||||
promslog.NewNopLogger(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
test.AssertNotifyLeaksNoSecret(ctx, t, notifier, key)
|
||||
}
|
||||
|
||||
func TestPagerDutyV1ServiceKeyFromFile(t *testing.T) {
|
||||
key := "01234567890123456789012345678901"
|
||||
f, err := os.CreateTemp(t.TempDir(), "pagerduty_test")
|
||||
require.NoError(t, err, "creating temp file failed")
|
||||
_, err = f.WriteString(key)
|
||||
require.NoError(t, err, "writing to temp file failed")
|
||||
|
||||
ctx, u, fn := test.GetContextWithCancelingURL()
|
||||
defer fn()
|
||||
|
||||
notifier, err := New(
|
||||
&config.PagerdutyConfig{
|
||||
ServiceKeyFile: f.Name(),
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
},
|
||||
test.CreateTmpl(t),
|
||||
promslog.NewNopLogger(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
notifier.apiV1 = u.String()
|
||||
|
||||
test.AssertNotifyLeaksNoSecret(ctx, t, notifier, key)
|
||||
}
|
||||
|
||||
func TestPagerDutyV2RoutingKeyFromFile(t *testing.T) {
|
||||
key := "01234567890123456789012345678901"
|
||||
f, err := os.CreateTemp(t.TempDir(), "pagerduty_test")
|
||||
require.NoError(t, err, "creating temp file failed")
|
||||
_, err = f.WriteString(key)
|
||||
require.NoError(t, err, "writing to temp file failed")
|
||||
|
||||
ctx, u, fn := test.GetContextWithCancelingURL()
|
||||
defer fn()
|
||||
|
||||
notifier, err := New(
|
||||
&config.PagerdutyConfig{
|
||||
URL: &config.URL{URL: u},
|
||||
RoutingKeyFile: f.Name(),
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
},
|
||||
test.CreateTmpl(t),
|
||||
promslog.NewNopLogger(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
test.AssertNotifyLeaksNoSecret(ctx, t, notifier, key)
|
||||
}
|
||||
|
||||
func TestPagerDutyTemplating(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
dec := json.NewDecoder(r.Body)
|
||||
out := make(map[string]any)
|
||||
err := dec.Decode(&out)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}))
|
||||
defer srv.Close()
|
||||
u, _ := url.Parse(srv.URL)
|
||||
|
||||
for _, tc := range []struct {
|
||||
title string
|
||||
cfg *config.PagerdutyConfig
|
||||
|
||||
retry bool
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
title: "full-blown legacy message",
|
||||
cfg: &config.PagerdutyConfig{
|
||||
RoutingKey: config.Secret("01234567890123456789012345678901"),
|
||||
Images: []config.PagerdutyImage{
|
||||
{
|
||||
Src: "{{ .Status }}",
|
||||
Alt: "{{ .Status }}",
|
||||
Href: "{{ .Status }}",
|
||||
},
|
||||
},
|
||||
Links: []config.PagerdutyLink{
|
||||
{
|
||||
Href: "{{ .Status }}",
|
||||
Text: "{{ .Status }}",
|
||||
},
|
||||
},
|
||||
Details: map[string]any{
|
||||
"firing": `{{ .Alerts.Firing | toJson }}`,
|
||||
"resolved": `{{ .Alerts.Resolved | toJson }}`,
|
||||
"num_firing": `{{ .Alerts.Firing | len }}`,
|
||||
"num_resolved": `{{ .Alerts.Resolved | len }}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "full-blown legacy message",
|
||||
cfg: &config.PagerdutyConfig{
|
||||
RoutingKey: config.Secret("01234567890123456789012345678901"),
|
||||
Images: []config.PagerdutyImage{
|
||||
{
|
||||
Src: "{{ .Status }}",
|
||||
Alt: "{{ .Status }}",
|
||||
Href: "{{ .Status }}",
|
||||
},
|
||||
},
|
||||
Links: []config.PagerdutyLink{
|
||||
{
|
||||
Href: "{{ .Status }}",
|
||||
Text: "{{ .Status }}",
|
||||
},
|
||||
},
|
||||
Details: map[string]any{
|
||||
"firing": `{{ template "pagerduty.default.instances" .Alerts.Firing }}`,
|
||||
"resolved": `{{ template "pagerduty.default.instances" .Alerts.Resolved }}`,
|
||||
"num_firing": `{{ .Alerts.Firing | len }}`,
|
||||
"num_resolved": `{{ .Alerts.Resolved | len }}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "nested details",
|
||||
cfg: &config.PagerdutyConfig{
|
||||
RoutingKey: config.Secret("01234567890123456789012345678901"),
|
||||
Details: map[string]any{
|
||||
"a": map[string]any{
|
||||
"b": map[string]any{
|
||||
"c": map[string]any{
|
||||
"firing": `{{ .Alerts.Firing | toJson }}`,
|
||||
"resolved": `{{ .Alerts.Resolved | toJson }}`,
|
||||
"num_firing": `{{ .Alerts.Firing | len }}`,
|
||||
"num_resolved": `{{ .Alerts.Resolved | len }}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "nested details with template error",
|
||||
cfg: &config.PagerdutyConfig{
|
||||
RoutingKey: config.Secret("01234567890123456789012345678901"),
|
||||
Details: map[string]any{
|
||||
"a": map[string]any{
|
||||
"b": map[string]any{
|
||||
"c": map[string]any{
|
||||
"firing": `{{ template "pagerduty.default.instances" .Alerts.Firing`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
errMsg: "failed to render details: template: :1: unclosed action",
|
||||
},
|
||||
{
|
||||
title: "details with templating errors",
|
||||
cfg: &config.PagerdutyConfig{
|
||||
RoutingKey: config.Secret("01234567890123456789012345678901"),
|
||||
Details: map[string]any{
|
||||
"firing": `{{ .Alerts.Firing | toJson`,
|
||||
"resolved": `{{ .Alerts.Resolved | toJson }}`,
|
||||
"num_firing": `{{ .Alerts.Firing | len }}`,
|
||||
"num_resolved": `{{ .Alerts.Resolved | len }}`,
|
||||
},
|
||||
},
|
||||
errMsg: "failed to render details: template: :1: unclosed action",
|
||||
},
|
||||
{
|
||||
title: "v2 message with templating errors",
|
||||
cfg: &config.PagerdutyConfig{
|
||||
RoutingKey: config.Secret("01234567890123456789012345678901"),
|
||||
Severity: "{{ ",
|
||||
},
|
||||
errMsg: "failed to template",
|
||||
},
|
||||
{
|
||||
title: "v1 message with templating errors",
|
||||
cfg: &config.PagerdutyConfig{
|
||||
ServiceKey: config.Secret("01234567890123456789012345678901"),
|
||||
Client: "{{ ",
|
||||
},
|
||||
errMsg: "failed to template",
|
||||
},
|
||||
{
|
||||
title: "routing key cannot be empty",
|
||||
cfg: &config.PagerdutyConfig{
|
||||
RoutingKey: config.Secret(`{{ "" }}`),
|
||||
},
|
||||
errMsg: "routing key cannot be empty",
|
||||
},
|
||||
{
|
||||
title: "service_key cannot be empty",
|
||||
cfg: &config.PagerdutyConfig{
|
||||
ServiceKey: config.Secret(`{{ "" }}`),
|
||||
},
|
||||
errMsg: "service key cannot be empty",
|
||||
},
|
||||
} {
|
||||
t.Run(tc.title, func(t *testing.T) {
|
||||
tc.cfg.URL = &config.URL{URL: u}
|
||||
tc.cfg.HTTPConfig = &commoncfg.HTTPClientConfig{}
|
||||
pd, err := New(tc.cfg, test.CreateTmpl(t), promslog.NewNopLogger())
|
||||
require.NoError(t, err)
|
||||
if pd.apiV1 != "" {
|
||||
pd.apiV1 = u.String()
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = notify.WithGroupKey(ctx, "1")
|
||||
|
||||
ok, err := pd.Notify(ctx, []*types.Alert{
|
||||
{
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{
|
||||
"lbl1": "val1",
|
||||
},
|
||||
StartsAt: time.Now(),
|
||||
EndsAt: time.Now().Add(time.Hour),
|
||||
},
|
||||
},
|
||||
}...)
|
||||
if tc.errMsg == "" {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
if errors.Asc(err, errors.CodeInternal) {
|
||||
_, _, errMsg, _, _, _ := errors.Unwrapb(err)
|
||||
require.Contains(t, errMsg, tc.errMsg)
|
||||
} else {
|
||||
require.Contains(t, err.Error(), tc.errMsg)
|
||||
}
|
||||
}
|
||||
require.Equal(t, tc.retry, ok)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrDetails(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
status int
|
||||
body io.Reader
|
||||
|
||||
exp string
|
||||
}{
|
||||
{
|
||||
status: http.StatusBadRequest,
|
||||
body: bytes.NewBuffer([]byte(
|
||||
`{"status":"invalid event","message":"Event object is invalid","errors":["Length of 'routing_key' is incorrect (should be 32 characters)"]}`,
|
||||
)),
|
||||
|
||||
exp: "Length of 'routing_key' is incorrect",
|
||||
},
|
||||
{
|
||||
status: http.StatusBadRequest,
|
||||
body: bytes.NewBuffer([]byte(`{"status"}`)),
|
||||
|
||||
exp: "",
|
||||
},
|
||||
{
|
||||
status: http.StatusBadRequest,
|
||||
|
||||
exp: "",
|
||||
},
|
||||
{
|
||||
status: http.StatusTooManyRequests,
|
||||
|
||||
exp: "",
|
||||
},
|
||||
} {
|
||||
t.Run("", func(t *testing.T) {
|
||||
err := errDetails(tc.status, tc.body)
|
||||
require.Contains(t, err, tc.exp)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventSizeEnforcement(t *testing.T) {
|
||||
bigDetailsV1 := map[string]any{
|
||||
"firing": strings.Repeat("a", 513000),
|
||||
}
|
||||
bigDetailsV2 := map[string]any{
|
||||
"firing": strings.Repeat("a", 513000),
|
||||
}
|
||||
|
||||
// V1 Messages
|
||||
msgV1 := &pagerDutyMessage{
|
||||
ServiceKey: "01234567890123456789012345678901",
|
||||
EventType: "trigger",
|
||||
Details: bigDetailsV1,
|
||||
}
|
||||
|
||||
notifierV1, err := New(
|
||||
&config.PagerdutyConfig{
|
||||
ServiceKey: config.Secret("01234567890123456789012345678901"),
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
},
|
||||
test.CreateTmpl(t),
|
||||
promslog.NewNopLogger(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
encodedV1, err := notifierV1.encodeMessage(context.Background(), msgV1)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, encodedV1.String(), `"details":{"error":"Custom details have been removed because the original event exceeds the maximum size of 512KB"}`)
|
||||
|
||||
// V2 Messages
|
||||
msgV2 := &pagerDutyMessage{
|
||||
RoutingKey: "01234567890123456789012345678901",
|
||||
EventAction: "trigger",
|
||||
Payload: &pagerDutyPayload{
|
||||
CustomDetails: bigDetailsV2,
|
||||
},
|
||||
}
|
||||
|
||||
notifierV2, err := New(
|
||||
&config.PagerdutyConfig{
|
||||
RoutingKey: config.Secret("01234567890123456789012345678901"),
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
},
|
||||
test.CreateTmpl(t),
|
||||
promslog.NewNopLogger(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
encodedV2, err := notifierV2.encodeMessage(context.Background(), msgV2)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, encodedV2.String(), `"custom_details":{"error":"Custom details have been removed because the original event exceeds the maximum size of 512KB"}`)
|
||||
}
|
||||
|
||||
func TestPagerDutyEmptySrcHref(t *testing.T) {
|
||||
type pagerDutyEvent struct {
|
||||
RoutingKey string `json:"routing_key"`
|
||||
EventAction string `json:"event_action"`
|
||||
DedupKey string `json:"dedup_key"`
|
||||
Payload pagerDutyPayload `json:"payload"`
|
||||
Images []pagerDutyImage
|
||||
Links []pagerDutyLink
|
||||
}
|
||||
|
||||
images := []config.PagerdutyImage{
|
||||
{
|
||||
Src: "",
|
||||
Alt: "Empty src",
|
||||
Href: "https://example.com/",
|
||||
},
|
||||
{
|
||||
Src: "https://example.com/cat.jpg",
|
||||
Alt: "Empty href",
|
||||
Href: "",
|
||||
},
|
||||
{
|
||||
Src: "https://example.com/cat.jpg",
|
||||
Alt: "",
|
||||
Href: "https://example.com/",
|
||||
},
|
||||
}
|
||||
|
||||
links := []config.PagerdutyLink{
|
||||
{
|
||||
Href: "",
|
||||
Text: "Empty href",
|
||||
},
|
||||
{
|
||||
Href: "https://example.com/",
|
||||
Text: "",
|
||||
},
|
||||
}
|
||||
|
||||
expectedImages := make([]pagerDutyImage, 0, len(images))
|
||||
for _, image := range images {
|
||||
if image.Src == "" {
|
||||
continue
|
||||
}
|
||||
expectedImages = append(expectedImages, pagerDutyImage{
|
||||
Src: image.Src,
|
||||
Alt: image.Alt,
|
||||
Href: image.Href,
|
||||
})
|
||||
}
|
||||
|
||||
expectedLinks := make([]pagerDutyLink, 0, len(links))
|
||||
for _, link := range links {
|
||||
if link.Href == "" {
|
||||
continue
|
||||
}
|
||||
expectedLinks = append(expectedLinks, pagerDutyLink{
|
||||
HRef: link.Href,
|
||||
Text: link.Text,
|
||||
})
|
||||
}
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
var event pagerDutyEvent
|
||||
if err := decoder.Decode(&event); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if event.RoutingKey == "" || event.EventAction == "" {
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
for _, image := range event.Images {
|
||||
if image.Src == "" {
|
||||
http.Error(w, "Event object is invalid: 'image src' is missing or blank", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for _, link := range event.Links {
|
||||
if link.HRef == "" {
|
||||
http.Error(w, "Event object is invalid: 'link href' is missing or blank", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
require.Equal(t, expectedImages, event.Images)
|
||||
require.Equal(t, expectedLinks, event.Links)
|
||||
},
|
||||
))
|
||||
defer server.Close()
|
||||
|
||||
url, err := url.Parse(server.URL)
|
||||
require.NoError(t, err)
|
||||
|
||||
pagerDutyConfig := config.PagerdutyConfig{
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
RoutingKey: config.Secret("01234567890123456789012345678901"),
|
||||
URL: &config.URL{URL: url},
|
||||
Images: images,
|
||||
Links: links,
|
||||
}
|
||||
|
||||
pagerDuty, err := New(&pagerDutyConfig, test.CreateTmpl(t), promslog.NewNopLogger())
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = notify.WithGroupKey(ctx, "1")
|
||||
|
||||
_, err = pagerDuty.Notify(ctx, []*types.Alert{
|
||||
{
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{
|
||||
"lbl1": "val1",
|
||||
},
|
||||
StartsAt: time.Now(),
|
||||
EndsAt: time.Now().Add(time.Hour),
|
||||
},
|
||||
},
|
||||
}...)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestPagerDutyTimeout(t *testing.T) {
|
||||
type pagerDutyEvent struct {
|
||||
RoutingKey string `json:"routing_key"`
|
||||
EventAction string `json:"event_action"`
|
||||
DedupKey string `json:"dedup_key"`
|
||||
Payload pagerDutyPayload `json:"payload"`
|
||||
Images []pagerDutyImage
|
||||
Links []pagerDutyLink
|
||||
}
|
||||
|
||||
tests := map[string]struct {
|
||||
latency time.Duration
|
||||
timeout time.Duration
|
||||
wantErr bool
|
||||
}{
|
||||
"success": {latency: 100 * time.Millisecond, timeout: 120 * time.Millisecond, wantErr: false},
|
||||
"error": {latency: 100 * time.Millisecond, timeout: 80 * time.Millisecond, wantErr: true},
|
||||
}
|
||||
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
var event pagerDutyEvent
|
||||
if err := decoder.Decode(&event); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if event.RoutingKey == "" || event.EventAction == "" {
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
time.Sleep(tt.latency)
|
||||
},
|
||||
))
|
||||
defer srv.Close()
|
||||
u, err := url.Parse(srv.URL)
|
||||
require.NoError(t, err)
|
||||
|
||||
cfg := config.PagerdutyConfig{
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
RoutingKey: config.Secret("01234567890123456789012345678901"),
|
||||
URL: &config.URL{URL: u},
|
||||
Timeout: tt.timeout,
|
||||
}
|
||||
|
||||
pd, err := New(&cfg, test.CreateTmpl(t), promslog.NewNopLogger())
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = notify.WithGroupKey(ctx, "1")
|
||||
alert := &types.Alert{
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{
|
||||
"lbl1": "val1",
|
||||
},
|
||||
StartsAt: time.Now(),
|
||||
EndsAt: time.Now().Add(time.Hour),
|
||||
},
|
||||
}
|
||||
_, err = pd.Notify(ctx, alert)
|
||||
require.Equal(t, tt.wantErr, err != nil)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderDetails(t *testing.T) {
|
||||
type args struct {
|
||||
details map[string]any
|
||||
data *template.Data
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want map[string]any
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "flat",
|
||||
args: args{
|
||||
details: map[string]any{
|
||||
"a": "{{ .Status }}",
|
||||
"b": "String",
|
||||
},
|
||||
data: &template.Data{
|
||||
Status: "Flat",
|
||||
},
|
||||
},
|
||||
want: map[string]any{
|
||||
"a": "Flat",
|
||||
"b": "String",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "flat error",
|
||||
args: args{
|
||||
details: map[string]any{
|
||||
"a": "{{ .Status",
|
||||
},
|
||||
data: &template.Data{
|
||||
Status: "Error",
|
||||
},
|
||||
},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "nested",
|
||||
args: args{
|
||||
details: map[string]any{
|
||||
"a": map[string]any{
|
||||
"b": map[string]any{
|
||||
"c": "{{ .Status }}",
|
||||
"d": "String",
|
||||
},
|
||||
},
|
||||
},
|
||||
data: &template.Data{
|
||||
Status: "Nested",
|
||||
},
|
||||
},
|
||||
want: map[string]any{
|
||||
"a": map[string]any{
|
||||
"b": map[string]any{
|
||||
"c": "Nested",
|
||||
"d": "String",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "nested error",
|
||||
args: args{
|
||||
details: map[string]any{
|
||||
"a": map[string]any{
|
||||
"b": map[string]any{
|
||||
"c": "{{ .Status",
|
||||
},
|
||||
},
|
||||
},
|
||||
data: &template.Data{
|
||||
Status: "Error",
|
||||
},
|
||||
},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "alerts",
|
||||
args: args{
|
||||
details: map[string]any{
|
||||
"alerts": map[string]any{
|
||||
"firing": "{{ .Alerts.Firing | toJson }}",
|
||||
"resolved": "{{ .Alerts.Resolved | toJson }}",
|
||||
"num_firing": "{{ len .Alerts.Firing }}",
|
||||
"num_resolved": "{{ len .Alerts.Resolved }}",
|
||||
},
|
||||
},
|
||||
data: &template.Data{
|
||||
Alerts: template.Alerts{
|
||||
{
|
||||
Status: "firing",
|
||||
Annotations: template.KV{
|
||||
"annotation1": "value1",
|
||||
"annotation2": "value2",
|
||||
},
|
||||
Labels: template.KV{
|
||||
"alertname": "Firing1",
|
||||
"label1": "value1",
|
||||
"label2": "value2",
|
||||
},
|
||||
Fingerprint: "fingerprint1",
|
||||
GeneratorURL: "http://generator1",
|
||||
StartsAt: time.Date(2001, time.January, 1, 0, 0, 0, 0, time.UTC),
|
||||
EndsAt: time.Date(2001, time.January, 1, 1, 0, 0, 0, time.UTC),
|
||||
},
|
||||
{
|
||||
Status: "firing",
|
||||
Annotations: template.KV{
|
||||
"annotation1": "value1",
|
||||
"annotation2": "value2",
|
||||
},
|
||||
Labels: template.KV{
|
||||
"alertname": "Firing2",
|
||||
"label1": "value1",
|
||||
"label2": "value2",
|
||||
},
|
||||
Fingerprint: "fingerprint2",
|
||||
GeneratorURL: "http://generator2",
|
||||
StartsAt: time.Date(2002, time.January, 1, 0, 0, 0, 0, time.UTC),
|
||||
EndsAt: time.Date(2002, time.January, 1, 1, 0, 0, 0, time.UTC),
|
||||
},
|
||||
{
|
||||
Status: "resolved",
|
||||
Annotations: template.KV{
|
||||
"annotation1": "value1",
|
||||
"annotation2": "value2",
|
||||
},
|
||||
Labels: template.KV{
|
||||
"alertname": "Resolved1",
|
||||
"label1": "value1",
|
||||
"label2": "value2",
|
||||
},
|
||||
Fingerprint: "fingerprint3",
|
||||
GeneratorURL: "http://generator3",
|
||||
StartsAt: time.Date(2001, time.January, 1, 0, 0, 0, 0, time.UTC),
|
||||
EndsAt: time.Date(2001, time.January, 1, 1, 0, 0, 0, time.UTC),
|
||||
},
|
||||
{
|
||||
Status: "resolved",
|
||||
Annotations: template.KV{
|
||||
"annotation1": "value1",
|
||||
"annotation2": "value2",
|
||||
},
|
||||
Labels: template.KV{
|
||||
"alertname": "Resolved2",
|
||||
"label1": "value1",
|
||||
"label2": "value2",
|
||||
},
|
||||
Fingerprint: "fingerprint4",
|
||||
GeneratorURL: "http://generator4",
|
||||
StartsAt: time.Date(2002, time.January, 1, 0, 0, 0, 0, time.UTC),
|
||||
EndsAt: time.Date(2002, time.January, 1, 1, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: map[string]any{
|
||||
"alerts": map[string]any{
|
||||
"firing": []any{
|
||||
map[string]any{
|
||||
"status": "firing",
|
||||
"labels": map[string]any{
|
||||
"alertname": "Firing1",
|
||||
"label1": "value1",
|
||||
"label2": "value2",
|
||||
},
|
||||
"annotations": map[string]any{
|
||||
"annotation1": "value1",
|
||||
"annotation2": "value2",
|
||||
},
|
||||
"startsAt": time.Date(2001, time.January, 1, 0, 0, 0, 0, time.UTC).Format(time.RFC3339),
|
||||
"endsAt": time.Date(2001, time.January, 1, 1, 0, 0, 0, time.UTC).Format(time.RFC3339),
|
||||
"fingerprint": "fingerprint1",
|
||||
"generatorURL": "http://generator1",
|
||||
},
|
||||
map[string]any{
|
||||
"status": "firing",
|
||||
"labels": map[string]any{
|
||||
"alertname": "Firing2",
|
||||
"label1": "value1",
|
||||
"label2": "value2",
|
||||
},
|
||||
"annotations": map[string]any{
|
||||
"annotation1": "value1",
|
||||
"annotation2": "value2",
|
||||
},
|
||||
"startsAt": time.Date(2002, time.January, 1, 0, 0, 0, 0, time.UTC).Format(time.RFC3339),
|
||||
"endsAt": time.Date(2002, time.January, 1, 1, 0, 0, 0, time.UTC).Format(time.RFC3339),
|
||||
"fingerprint": "fingerprint2",
|
||||
"generatorURL": "http://generator2",
|
||||
},
|
||||
},
|
||||
"resolved": []any{
|
||||
map[string]any{
|
||||
"status": "resolved",
|
||||
"labels": map[string]any{
|
||||
"alertname": "Resolved1",
|
||||
"label1": "value1",
|
||||
"label2": "value2",
|
||||
},
|
||||
"annotations": map[string]any{
|
||||
"annotation1": "value1",
|
||||
"annotation2": "value2",
|
||||
},
|
||||
"startsAt": time.Date(2001, time.January, 1, 0, 0, 0, 0, time.UTC).Format(time.RFC3339),
|
||||
"endsAt": time.Date(2001, time.January, 1, 1, 0, 0, 0, time.UTC).Format(time.RFC3339),
|
||||
"fingerprint": "fingerprint3",
|
||||
"generatorURL": "http://generator3",
|
||||
},
|
||||
map[string]any{
|
||||
"status": "resolved",
|
||||
"labels": map[string]any{
|
||||
"alertname": "Resolved2",
|
||||
"label1": "value1",
|
||||
"label2": "value2",
|
||||
},
|
||||
"annotations": map[string]any{
|
||||
"annotation1": "value1",
|
||||
"annotation2": "value2",
|
||||
},
|
||||
"startsAt": time.Date(2002, time.January, 1, 0, 0, 0, 0, time.UTC).Format(time.RFC3339),
|
||||
"endsAt": time.Date(2002, time.January, 1, 1, 0, 0, 0, time.UTC).Format(time.RFC3339),
|
||||
"fingerprint": "fingerprint4",
|
||||
"generatorURL": "http://generator4",
|
||||
},
|
||||
},
|
||||
"num_firing": 2,
|
||||
"num_resolved": 2,
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
n := &Notifier{
|
||||
conf: &config.PagerdutyConfig{
|
||||
Details: tt.args.details,
|
||||
},
|
||||
tmpl: test.CreateTmpl(t),
|
||||
}
|
||||
got, err := n.renderDetails(tt.args.data)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("renderDetails() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
require.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,8 @@ package alertmanagernotify
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"slices"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagernotify/email"
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagernotify/msteamsv2"
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagernotify/opsgenie"
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagernotify/pagerduty"
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagernotify/slack"
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagernotify/webhook"
|
||||
"github.com/SigNoz/signoz/pkg/types/alertmanagertypes"
|
||||
"github.com/prometheus/alertmanager/config/receiver"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
@@ -17,15 +11,6 @@ import (
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
)
|
||||
|
||||
var customNotifierIntegrations = []string{
|
||||
webhook.Integration,
|
||||
email.Integration,
|
||||
pagerduty.Integration,
|
||||
opsgenie.Integration,
|
||||
slack.Integration,
|
||||
msteamsv2.Integration,
|
||||
}
|
||||
|
||||
func NewReceiverIntegrations(nc alertmanagertypes.Receiver, tmpl *template.Template, logger *slog.Logger) ([]notify.Integration, error) {
|
||||
upstreamIntegrations, err := receiver.BuildReceiverIntegrations(nc, tmpl, logger)
|
||||
if err != nil {
|
||||
@@ -46,29 +31,14 @@ func NewReceiverIntegrations(nc alertmanagertypes.Receiver, tmpl *template.Templ
|
||||
)
|
||||
|
||||
for _, integration := range upstreamIntegrations {
|
||||
// skip upstream integration if we support custom integration for it
|
||||
if !slices.Contains(customNotifierIntegrations, integration.Name()) {
|
||||
// skip upstream msteamsv2 integration
|
||||
if integration.Name() != "msteamsv2" {
|
||||
integrations = append(integrations, integration)
|
||||
}
|
||||
}
|
||||
|
||||
for i, c := range nc.WebhookConfigs {
|
||||
add(webhook.Integration, i, c, func(l *slog.Logger) (notify.Notifier, error) { return webhook.New(c, tmpl, l) })
|
||||
}
|
||||
for i, c := range nc.EmailConfigs {
|
||||
add(email.Integration, i, c, func(l *slog.Logger) (notify.Notifier, error) { return email.New(c, tmpl, l), nil })
|
||||
}
|
||||
for i, c := range nc.PagerdutyConfigs {
|
||||
add(pagerduty.Integration, i, c, func(l *slog.Logger) (notify.Notifier, error) { return pagerduty.New(c, tmpl, l) })
|
||||
}
|
||||
for i, c := range nc.OpsGenieConfigs {
|
||||
add(opsgenie.Integration, i, c, func(l *slog.Logger) (notify.Notifier, error) { return opsgenie.New(c, tmpl, l) })
|
||||
}
|
||||
for i, c := range nc.SlackConfigs {
|
||||
add(slack.Integration, i, c, func(l *slog.Logger) (notify.Notifier, error) { return slack.New(c, tmpl, l) })
|
||||
}
|
||||
for i, c := range nc.MSTeamsV2Configs {
|
||||
add(msteamsv2.Integration, i, c, func(l *slog.Logger) (notify.Notifier, error) {
|
||||
add("msteamsv2", i, c, func(l *slog.Logger) (notify.Notifier, error) {
|
||||
return msteamsv2.New(c, tmpl, `{{ template "msteamsv2.default.titleLink" . }}`, l)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,278 +0,0 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
commoncfg "github.com/prometheus/common/config"
|
||||
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
)
|
||||
|
||||
const (
|
||||
Integration = "slack"
|
||||
)
|
||||
|
||||
// https://api.slack.com/reference/messaging/attachments#legacy_fields - 1024, no units given, assuming runes or characters.
|
||||
const maxTitleLenRunes = 1024
|
||||
|
||||
// Notifier implements a Notifier for Slack notifications.
|
||||
type Notifier struct {
|
||||
conf *config.SlackConfig
|
||||
tmpl *template.Template
|
||||
logger *slog.Logger
|
||||
client *http.Client
|
||||
retrier *notify.Retrier
|
||||
|
||||
postJSONFunc func(ctx context.Context, client *http.Client, url string, body io.Reader) (*http.Response, error)
|
||||
}
|
||||
|
||||
// New returns a new Slack notification handler.
|
||||
func New(c *config.SlackConfig, t *template.Template, l *slog.Logger, httpOpts ...commoncfg.HTTPClientOption) (*Notifier, error) {
|
||||
client, err := notify.NewClientWithTracing(*c.HTTPConfig, Integration, httpOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Notifier{
|
||||
conf: c,
|
||||
tmpl: t,
|
||||
logger: l,
|
||||
client: client,
|
||||
retrier: ¬ify.Retrier{},
|
||||
postJSONFunc: notify.PostJSON,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// request is the request for sending a slack notification.
|
||||
type request struct {
|
||||
Channel string `json:"channel,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
IconEmoji string `json:"icon_emoji,omitempty"`
|
||||
IconURL string `json:"icon_url,omitempty"`
|
||||
LinkNames bool `json:"link_names,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
Attachments []attachment `json:"attachments"`
|
||||
}
|
||||
|
||||
// attachment is used to display a richly-formatted message block.
|
||||
type attachment struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
TitleLink string `json:"title_link,omitempty"`
|
||||
Pretext string `json:"pretext,omitempty"`
|
||||
Text string `json:"text"`
|
||||
Fallback string `json:"fallback"`
|
||||
CallbackID string `json:"callback_id"`
|
||||
Fields []config.SlackField `json:"fields,omitempty"`
|
||||
Actions []config.SlackAction `json:"actions,omitempty"`
|
||||
ImageURL string `json:"image_url,omitempty"`
|
||||
ThumbURL string `json:"thumb_url,omitempty"`
|
||||
Footer string `json:"footer"`
|
||||
Color string `json:"color,omitempty"`
|
||||
MrkdwnIn []string `json:"mrkdwn_in,omitempty"`
|
||||
}
|
||||
|
||||
// Notify implements the Notifier interface.
|
||||
func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
|
||||
var err error
|
||||
|
||||
key, err := notify.ExtractGroupKey(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
logger := n.logger.With("group_key", key)
|
||||
logger.DebugContext(ctx, "extracted group key")
|
||||
|
||||
var (
|
||||
data = notify.GetTemplateData(ctx, n.tmpl, as, logger)
|
||||
tmplText = notify.TmplText(n.tmpl, data, &err)
|
||||
)
|
||||
var markdownIn []string
|
||||
|
||||
if len(n.conf.MrkdwnIn) == 0 {
|
||||
markdownIn = []string{"fallback", "pretext", "text"}
|
||||
} else {
|
||||
markdownIn = n.conf.MrkdwnIn
|
||||
}
|
||||
|
||||
title, truncated := notify.TruncateInRunes(tmplText(n.conf.Title), maxTitleLenRunes)
|
||||
if truncated {
|
||||
logger.WarnContext(ctx, "Truncated title", "max_runes", maxTitleLenRunes)
|
||||
}
|
||||
att := &attachment{
|
||||
Title: title,
|
||||
TitleLink: tmplText(n.conf.TitleLink),
|
||||
Pretext: tmplText(n.conf.Pretext),
|
||||
Text: tmplText(n.conf.Text),
|
||||
Fallback: tmplText(n.conf.Fallback),
|
||||
CallbackID: tmplText(n.conf.CallbackID),
|
||||
ImageURL: tmplText(n.conf.ImageURL),
|
||||
ThumbURL: tmplText(n.conf.ThumbURL),
|
||||
Footer: tmplText(n.conf.Footer),
|
||||
Color: tmplText(n.conf.Color),
|
||||
MrkdwnIn: markdownIn,
|
||||
}
|
||||
|
||||
numFields := len(n.conf.Fields)
|
||||
if numFields > 0 {
|
||||
fields := make([]config.SlackField, numFields)
|
||||
for index, field := range n.conf.Fields {
|
||||
// Check if short was defined for the field otherwise fallback to the global setting
|
||||
var short bool
|
||||
if field.Short != nil {
|
||||
short = *field.Short
|
||||
} else {
|
||||
short = n.conf.ShortFields
|
||||
}
|
||||
|
||||
// Rebuild the field by executing any templates and setting the new value for short
|
||||
fields[index] = config.SlackField{
|
||||
Title: tmplText(field.Title),
|
||||
Value: tmplText(field.Value),
|
||||
Short: &short,
|
||||
}
|
||||
}
|
||||
att.Fields = fields
|
||||
}
|
||||
|
||||
numActions := len(n.conf.Actions)
|
||||
if numActions > 0 {
|
||||
actions := make([]config.SlackAction, numActions)
|
||||
for index, action := range n.conf.Actions {
|
||||
slackAction := config.SlackAction{
|
||||
Type: tmplText(action.Type),
|
||||
Text: tmplText(action.Text),
|
||||
URL: tmplText(action.URL),
|
||||
Style: tmplText(action.Style),
|
||||
Name: tmplText(action.Name),
|
||||
Value: tmplText(action.Value),
|
||||
}
|
||||
|
||||
if action.ConfirmField != nil {
|
||||
slackAction.ConfirmField = &config.SlackConfirmationField{
|
||||
Title: tmplText(action.ConfirmField.Title),
|
||||
Text: tmplText(action.ConfirmField.Text),
|
||||
OkText: tmplText(action.ConfirmField.OkText),
|
||||
DismissText: tmplText(action.ConfirmField.DismissText),
|
||||
}
|
||||
}
|
||||
|
||||
actions[index] = slackAction
|
||||
}
|
||||
att.Actions = actions
|
||||
}
|
||||
|
||||
req := &request{
|
||||
Channel: tmplText(n.conf.Channel),
|
||||
Username: tmplText(n.conf.Username),
|
||||
IconEmoji: tmplText(n.conf.IconEmoji),
|
||||
IconURL: tmplText(n.conf.IconURL),
|
||||
LinkNames: n.conf.LinkNames,
|
||||
Text: tmplText(n.conf.MessageText),
|
||||
Attachments: []attachment{*att},
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := json.NewEncoder(&buf).Encode(req); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var u string
|
||||
if n.conf.APIURL != nil {
|
||||
u = n.conf.APIURL.String()
|
||||
} else {
|
||||
content, err := os.ReadFile(n.conf.APIURLFile)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
u = strings.TrimSpace(string(content))
|
||||
}
|
||||
|
||||
if n.conf.Timeout > 0 {
|
||||
postCtx, cancel := context.WithTimeoutCause(ctx, n.conf.Timeout, errors.NewInternalf(errors.CodeTimeout, "configured slack timeout reached (%s)", n.conf.Timeout))
|
||||
defer cancel()
|
||||
ctx = postCtx
|
||||
}
|
||||
|
||||
resp, err := n.postJSONFunc(ctx, n.client, u, &buf) //nolint:bodyclose
|
||||
if err != nil {
|
||||
if ctx.Err() != nil {
|
||||
err = errors.NewInternalf(errors.CodeInternal, "failed to post JSON to slack: %v", context.Cause(ctx))
|
||||
}
|
||||
return true, notify.RedactURL(err)
|
||||
}
|
||||
defer notify.Drain(resp)
|
||||
|
||||
// Use a retrier to generate an error message for non-200 responses and
|
||||
// classify them as retriable or not.
|
||||
retry, err := n.retrier.Check(resp.StatusCode, resp.Body)
|
||||
if err != nil {
|
||||
err = errors.NewInternalf(errors.CodeInternal, "channel %q: %v", req.Channel, err)
|
||||
return retry, notify.NewErrorWithReason(notify.GetFailureReasonFromStatusCode(resp.StatusCode), err)
|
||||
}
|
||||
|
||||
// Slack web API might return errors with a 200 response code.
|
||||
// https://slack.dev/node-slack-sdk/web-api#handle-errors
|
||||
retry, err = checkResponseError(resp)
|
||||
if err != nil {
|
||||
err = errors.NewInternalf(errors.CodeInternal, "channel %q: %v", req.Channel, err)
|
||||
return retry, notify.NewErrorWithReason(notify.ClientErrorReason, err)
|
||||
}
|
||||
|
||||
return retry, nil
|
||||
}
|
||||
|
||||
// checkResponseError parses out the error message from Slack API response.
|
||||
func checkResponseError(resp *http.Response) (bool, error) {
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "could not read response body")
|
||||
}
|
||||
|
||||
if strings.HasPrefix(resp.Header.Get("Content-Type"), "application/json") {
|
||||
return checkJSONResponseError(body)
|
||||
}
|
||||
return checkTextResponseError(body)
|
||||
}
|
||||
|
||||
// checkTextResponseError classifies plaintext responses from Slack.
|
||||
// A plaintext (non-JSON) response is successful if it's a string "ok".
|
||||
// This is typically a response for an Incoming Webhook
|
||||
// (https://api.slack.com/messaging/webhooks#handling_errors)
|
||||
func checkTextResponseError(body []byte) (bool, error) {
|
||||
if !bytes.Equal(body, []byte("ok")) {
|
||||
return false, errors.NewInternalf(errors.CodeInternal, "received an error response from Slack: %s", string(body))
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// checkJSONResponseError classifies JSON responses from Slack.
|
||||
func checkJSONResponseError(body []byte) (bool, error) {
|
||||
// response is for parsing out errors from the JSON response.
|
||||
type response struct {
|
||||
OK bool `json:"ok"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
var data response
|
||||
if err := json.Unmarshal(body, &data); err != nil {
|
||||
return true, errors.NewInternalf(errors.CodeInternal, "could not unmarshal JSON response %q: %v", string(body), err)
|
||||
}
|
||||
if !data.OK {
|
||||
return false, errors.NewInternalf(errors.CodeInternal, "error response from Slack: %s", data.Error)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
@@ -1,339 +0,0 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
commoncfg "github.com/prometheus/common/config"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/prometheus/common/promslog"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/notify/test"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
)
|
||||
|
||||
func TestSlackRetry(t *testing.T) {
|
||||
notifier, err := New(
|
||||
&config.SlackConfig{
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
},
|
||||
test.CreateTmpl(t),
|
||||
promslog.NewNopLogger(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
for statusCode, expected := range test.RetryTests(test.DefaultRetryCodes()) {
|
||||
actual, _ := notifier.retrier.Check(statusCode, nil)
|
||||
require.Equal(t, expected, actual, "error on status %d", statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSlackRedactedURL(t *testing.T) {
|
||||
ctx, u, fn := test.GetContextWithCancelingURL()
|
||||
defer fn()
|
||||
|
||||
notifier, err := New(
|
||||
&config.SlackConfig{
|
||||
APIURL: &config.SecretURL{URL: u},
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
},
|
||||
test.CreateTmpl(t),
|
||||
promslog.NewNopLogger(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
test.AssertNotifyLeaksNoSecret(ctx, t, notifier, u.String())
|
||||
}
|
||||
|
||||
func TestGettingSlackURLFromFile(t *testing.T) {
|
||||
ctx, u, fn := test.GetContextWithCancelingURL()
|
||||
defer fn()
|
||||
|
||||
f, err := os.CreateTemp(t.TempDir(), "slack_test")
|
||||
require.NoError(t, err, "creating temp file failed")
|
||||
_, err = f.WriteString(u.String())
|
||||
require.NoError(t, err, "writing to temp file failed")
|
||||
|
||||
notifier, err := New(
|
||||
&config.SlackConfig{
|
||||
APIURLFile: f.Name(),
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
},
|
||||
test.CreateTmpl(t),
|
||||
promslog.NewNopLogger(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
test.AssertNotifyLeaksNoSecret(ctx, t, notifier, u.String())
|
||||
}
|
||||
|
||||
func TestTrimmingSlackURLFromFile(t *testing.T) {
|
||||
ctx, u, fn := test.GetContextWithCancelingURL()
|
||||
defer fn()
|
||||
|
||||
f, err := os.CreateTemp(t.TempDir(), "slack_test_newline")
|
||||
require.NoError(t, err, "creating temp file failed")
|
||||
_, err = f.WriteString(u.String() + "\n\n")
|
||||
require.NoError(t, err, "writing to temp file failed")
|
||||
|
||||
notifier, err := New(
|
||||
&config.SlackConfig{
|
||||
APIURLFile: f.Name(),
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
},
|
||||
test.CreateTmpl(t),
|
||||
promslog.NewNopLogger(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
test.AssertNotifyLeaksNoSecret(ctx, t, notifier, u.String())
|
||||
}
|
||||
|
||||
func TestNotifier_Notify_WithReason(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
statusCode int
|
||||
responseBody string
|
||||
expectedReason notify.Reason
|
||||
expectedErr string
|
||||
expectedRetry bool
|
||||
noError bool
|
||||
}{
|
||||
{
|
||||
name: "with a 4xx status code",
|
||||
statusCode: http.StatusUnauthorized,
|
||||
expectedReason: notify.ClientErrorReason,
|
||||
expectedRetry: false,
|
||||
expectedErr: "unexpected status code 401",
|
||||
},
|
||||
{
|
||||
name: "with a 5xx status code",
|
||||
statusCode: http.StatusInternalServerError,
|
||||
expectedReason: notify.ServerErrorReason,
|
||||
expectedRetry: true,
|
||||
expectedErr: "unexpected status code 500",
|
||||
},
|
||||
{
|
||||
name: "with a 3xx status code",
|
||||
statusCode: http.StatusTemporaryRedirect,
|
||||
expectedReason: notify.DefaultReason,
|
||||
expectedRetry: false,
|
||||
expectedErr: "unexpected status code 307",
|
||||
},
|
||||
{
|
||||
name: "with a 1xx status code",
|
||||
statusCode: http.StatusSwitchingProtocols,
|
||||
expectedReason: notify.DefaultReason,
|
||||
expectedRetry: false,
|
||||
expectedErr: "unexpected status code 101",
|
||||
},
|
||||
{
|
||||
name: "2xx response with invalid JSON",
|
||||
statusCode: http.StatusOK,
|
||||
responseBody: `{"not valid json"}`,
|
||||
expectedReason: notify.ClientErrorReason,
|
||||
expectedRetry: true,
|
||||
expectedErr: "could not unmarshal",
|
||||
},
|
||||
{
|
||||
name: "2xx response with a JSON error",
|
||||
statusCode: http.StatusOK,
|
||||
responseBody: `{"ok":false,"error":"error_message"}`,
|
||||
expectedReason: notify.ClientErrorReason,
|
||||
expectedRetry: false,
|
||||
expectedErr: "error response from Slack: error_message",
|
||||
},
|
||||
{
|
||||
name: "2xx response with a plaintext error",
|
||||
statusCode: http.StatusOK,
|
||||
responseBody: "no_channel",
|
||||
expectedReason: notify.ClientErrorReason,
|
||||
expectedRetry: false,
|
||||
expectedErr: "error response from Slack: no_channel",
|
||||
},
|
||||
{
|
||||
name: "successful JSON response",
|
||||
statusCode: http.StatusOK,
|
||||
responseBody: `{"ok":true}`,
|
||||
noError: true,
|
||||
},
|
||||
{
|
||||
name: "successful plaintext response",
|
||||
statusCode: http.StatusOK,
|
||||
responseBody: "ok",
|
||||
noError: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
apiurl, _ := url.Parse("https://slack.com/post.Message")
|
||||
notifier, err := New(
|
||||
&config.SlackConfig{
|
||||
NotifierConfig: config.NotifierConfig{},
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
APIURL: &config.SecretURL{URL: apiurl},
|
||||
Channel: "channelname",
|
||||
},
|
||||
test.CreateTmpl(t),
|
||||
promslog.NewNopLogger(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
notifier.postJSONFunc = func(ctx context.Context, client *http.Client, url string, body io.Reader) (*http.Response, error) {
|
||||
resp := httptest.NewRecorder()
|
||||
if strings.HasPrefix(tt.responseBody, "{") {
|
||||
resp.Header().Add("Content-Type", "application/json; charset=utf-8")
|
||||
}
|
||||
resp.WriteHeader(tt.statusCode)
|
||||
_, _ = resp.WriteString(tt.responseBody)
|
||||
return resp.Result(), nil
|
||||
}
|
||||
ctx := context.Background()
|
||||
ctx = notify.WithGroupKey(ctx, "1")
|
||||
|
||||
alert1 := &types.Alert{
|
||||
Alert: model.Alert{
|
||||
StartsAt: time.Now(),
|
||||
EndsAt: time.Now().Add(time.Hour),
|
||||
},
|
||||
}
|
||||
retry, err := notifier.Notify(ctx, alert1)
|
||||
require.Equal(t, tt.expectedRetry, retry)
|
||||
if tt.noError {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
var reasonError *notify.ErrorWithReason
|
||||
require.ErrorAs(t, err, &reasonError)
|
||||
require.Equal(t, tt.expectedReason, reasonError.Reason)
|
||||
require.Contains(t, err.Error(), tt.expectedErr)
|
||||
require.Contains(t, err.Error(), "channelname")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSlackTimeout(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
latency time.Duration
|
||||
timeout time.Duration
|
||||
wantErr bool
|
||||
}{
|
||||
"success": {latency: 100 * time.Millisecond, timeout: 120 * time.Millisecond, wantErr: false},
|
||||
"error": {latency: 100 * time.Millisecond, timeout: 80 * time.Millisecond, wantErr: true},
|
||||
}
|
||||
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
u, _ := url.Parse("https://slack.com/post.Message")
|
||||
notifier, err := New(
|
||||
&config.SlackConfig{
|
||||
NotifierConfig: config.NotifierConfig{},
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
APIURL: &config.SecretURL{URL: u},
|
||||
Channel: "channelname",
|
||||
Timeout: tt.timeout,
|
||||
},
|
||||
test.CreateTmpl(t),
|
||||
promslog.NewNopLogger(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
notifier.postJSONFunc = func(ctx context.Context, client *http.Client, url string, body io.Reader) (*http.Response, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-time.After(tt.latency):
|
||||
resp := httptest.NewRecorder()
|
||||
resp.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
resp.WriteHeader(http.StatusOK)
|
||||
_, _ = resp.WriteString(`{"ok":true}`)
|
||||
|
||||
return resp.Result(), nil
|
||||
}
|
||||
}
|
||||
ctx := context.Background()
|
||||
ctx = notify.WithGroupKey(ctx, "1")
|
||||
|
||||
alert := &types.Alert{
|
||||
Alert: model.Alert{
|
||||
StartsAt: time.Now(),
|
||||
EndsAt: time.Now().Add(time.Hour),
|
||||
},
|
||||
}
|
||||
_, err = notifier.Notify(ctx, alert)
|
||||
require.Equal(t, tt.wantErr, err != nil)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSlackMessageField(t *testing.T) {
|
||||
// 1. Setup a fake Slack server
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var body map[string]any
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// 2. VERIFY: Top-level text exists
|
||||
if body["text"] != "My Top Level Message" {
|
||||
t.Errorf("Expected top-level 'text' to be 'My Top Level Message', got %v", body["text"])
|
||||
}
|
||||
|
||||
// 3. VERIFY: Old attachments still exist
|
||||
attachments, ok := body["attachments"].([]any)
|
||||
if !ok || len(attachments) == 0 {
|
||||
t.Errorf("Expected attachments to exist")
|
||||
} else {
|
||||
first := attachments[0].(map[string]any)
|
||||
if first["title"] != "Old Attachment Title" {
|
||||
t.Errorf("Expected attachment title 'Old Attachment Title', got %v", first["title"])
|
||||
}
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte(`{"ok": true}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
// 4. Configure Notifier with BOTH new and old fields
|
||||
u, _ := url.Parse(server.URL)
|
||||
conf := &config.SlackConfig{
|
||||
APIURL: &config.SecretURL{URL: u},
|
||||
MessageText: "My Top Level Message", // Your NEW field
|
||||
Title: "Old Attachment Title", // An OLD field
|
||||
Channel: "#test-channel",
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
}
|
||||
|
||||
tmpl, err := template.FromGlobs([]string{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tmpl.ExternalURL = u
|
||||
|
||||
logger := slog.New(slog.DiscardHandler)
|
||||
notifier, err := New(conf, tmpl, logger)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = notify.WithGroupKey(ctx, "test-group-key")
|
||||
|
||||
if _, err := notifier.Notify(ctx); err != nil {
|
||||
t.Fatal("Notify failed:", err)
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
commoncfg "github.com/prometheus/common/config"
|
||||
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
)
|
||||
|
||||
const (
|
||||
Integration = "webhook"
|
||||
)
|
||||
|
||||
// Notifier implements a Notifier for generic webhooks.
|
||||
type Notifier struct {
|
||||
conf *config.WebhookConfig
|
||||
tmpl *template.Template
|
||||
logger *slog.Logger
|
||||
client *http.Client
|
||||
retrier *notify.Retrier
|
||||
}
|
||||
|
||||
// New returns a new Webhook.
|
||||
func New(conf *config.WebhookConfig, t *template.Template, l *slog.Logger, httpOpts ...commoncfg.HTTPClientOption) (*Notifier, error) {
|
||||
client, err := notify.NewClientWithTracing(*conf.HTTPConfig, Integration, httpOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Notifier{
|
||||
conf: conf,
|
||||
tmpl: t,
|
||||
logger: l,
|
||||
client: client,
|
||||
// Webhooks are assumed to respond with 2xx response codes on a successful
|
||||
// request and 5xx response codes are assumed to be recoverable.
|
||||
retrier: ¬ify.Retrier{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Message defines the JSON object send to webhook endpoints.
|
||||
type Message struct {
|
||||
*template.Data
|
||||
|
||||
// The protocol version.
|
||||
Version string `json:"version"`
|
||||
GroupKey string `json:"groupKey"`
|
||||
TruncatedAlerts uint64 `json:"truncatedAlerts"`
|
||||
}
|
||||
|
||||
func truncateAlerts(maxAlerts uint64, alerts []*types.Alert) ([]*types.Alert, uint64) {
|
||||
if maxAlerts != 0 && uint64(len(alerts)) > maxAlerts {
|
||||
return alerts[:maxAlerts], uint64(len(alerts)) - maxAlerts
|
||||
}
|
||||
|
||||
return alerts, 0
|
||||
}
|
||||
|
||||
// Notify implements the Notifier interface.
|
||||
func (n *Notifier) Notify(ctx context.Context, alerts ...*types.Alert) (bool, error) {
|
||||
alerts, numTruncated := truncateAlerts(n.conf.MaxAlerts, alerts)
|
||||
data := notify.GetTemplateData(ctx, n.tmpl, alerts, n.logger)
|
||||
|
||||
groupKey, err := notify.ExtractGroupKey(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
logger := n.logger.With("group_key", groupKey)
|
||||
logger.DebugContext(ctx, "extracted group key")
|
||||
|
||||
msg := &Message{
|
||||
Version: "4",
|
||||
Data: data,
|
||||
GroupKey: groupKey.String(),
|
||||
TruncatedAlerts: numTruncated,
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := json.NewEncoder(&buf).Encode(msg); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var url string
|
||||
var tmplErr error
|
||||
tmpl := notify.TmplText(n.tmpl, data, &tmplErr)
|
||||
|
||||
if n.conf.URL != "" {
|
||||
url = tmpl(string(n.conf.URL))
|
||||
} else {
|
||||
content, err := os.ReadFile(n.conf.URLFile)
|
||||
if err != nil {
|
||||
return false, errors.WrapInternalf(err, errors.CodeInternal, "read url_file")
|
||||
}
|
||||
url = tmpl(strings.TrimSpace(string(content)))
|
||||
}
|
||||
|
||||
if tmplErr != nil {
|
||||
return false, errors.NewInternalf(errors.CodeInternal, "failed to template webhook URL: %v", tmplErr)
|
||||
}
|
||||
|
||||
if url == "" {
|
||||
return false, errors.NewInternalf(errors.CodeInternal, "webhook URL is empty after templating")
|
||||
}
|
||||
|
||||
if n.conf.Timeout > 0 {
|
||||
postCtx, cancel := context.WithTimeoutCause(ctx, n.conf.Timeout, errors.NewInternalf(errors.CodeTimeout, "configured webhook timeout reached (%s)", n.conf.Timeout))
|
||||
defer cancel()
|
||||
ctx = postCtx
|
||||
}
|
||||
|
||||
resp, err := notify.PostJSON(ctx, n.client, url, &buf) //nolint:bodyclose
|
||||
if err != nil {
|
||||
if ctx.Err() != nil {
|
||||
err = errors.NewInternalf(errors.CodeInternal, "failed to post JSON to webhook: %v", context.Cause(ctx))
|
||||
}
|
||||
return true, notify.RedactURL(err)
|
||||
}
|
||||
defer notify.Drain(resp)
|
||||
|
||||
shouldRetry, err := n.retrier.Check(resp.StatusCode, resp.Body)
|
||||
if err != nil {
|
||||
return shouldRetry, notify.NewErrorWithReason(notify.GetFailureReasonFromStatusCode(resp.StatusCode), err)
|
||||
}
|
||||
return shouldRetry, err
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
commoncfg "github.com/prometheus/common/config"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/prometheus/common/promslog"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/notify/test"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
)
|
||||
|
||||
func TestWebhookRetry(t *testing.T) {
|
||||
notifier, err := New(
|
||||
&config.WebhookConfig{
|
||||
URL: config.SecretTemplateURL("http://example.com"),
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
},
|
||||
test.CreateTmpl(t),
|
||||
promslog.NewNopLogger(),
|
||||
)
|
||||
if err != nil {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
t.Run("test retry status code", func(t *testing.T) {
|
||||
for statusCode, expected := range test.RetryTests(test.DefaultRetryCodes()) {
|
||||
actual, _ := notifier.retrier.Check(statusCode, nil)
|
||||
require.Equal(t, expected, actual, "error on status %d", statusCode)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test retry error details", func(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
status int
|
||||
body io.Reader
|
||||
|
||||
exp string
|
||||
}{
|
||||
{
|
||||
status: http.StatusBadRequest,
|
||||
body: bytes.NewBuffer([]byte(
|
||||
`{"status":"invalid event"}`,
|
||||
)),
|
||||
|
||||
exp: fmt.Sprintf(`unexpected status code %d: {"status":"invalid event"}`, http.StatusBadRequest),
|
||||
},
|
||||
{
|
||||
status: http.StatusBadRequest,
|
||||
|
||||
exp: fmt.Sprintf(`unexpected status code %d`, http.StatusBadRequest),
|
||||
},
|
||||
} {
|
||||
t.Run("", func(t *testing.T) {
|
||||
_, err = notifier.retrier.Check(tc.status, tc.body)
|
||||
require.Equal(t, tc.exp, err.Error())
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestWebhookTruncateAlerts(t *testing.T) {
|
||||
alerts := make([]*types.Alert, 10)
|
||||
|
||||
truncatedAlerts, numTruncated := truncateAlerts(0, alerts)
|
||||
require.Len(t, truncatedAlerts, 10)
|
||||
require.EqualValues(t, 0, numTruncated)
|
||||
|
||||
truncatedAlerts, numTruncated = truncateAlerts(4, alerts)
|
||||
require.Len(t, truncatedAlerts, 4)
|
||||
require.EqualValues(t, 6, numTruncated)
|
||||
|
||||
truncatedAlerts, numTruncated = truncateAlerts(100, alerts)
|
||||
require.Len(t, truncatedAlerts, 10)
|
||||
require.EqualValues(t, 0, numTruncated)
|
||||
}
|
||||
|
||||
func TestWebhookRedactedURL(t *testing.T) {
|
||||
ctx, u, fn := test.GetContextWithCancelingURL()
|
||||
defer fn()
|
||||
|
||||
secret := "secret"
|
||||
notifier, err := New(
|
||||
&config.WebhookConfig{
|
||||
URL: config.SecretTemplateURL(u.String()),
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
},
|
||||
test.CreateTmpl(t),
|
||||
promslog.NewNopLogger(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
test.AssertNotifyLeaksNoSecret(ctx, t, notifier, secret)
|
||||
}
|
||||
|
||||
func TestWebhookReadingURLFromFile(t *testing.T) {
|
||||
ctx, u, fn := test.GetContextWithCancelingURL()
|
||||
defer fn()
|
||||
|
||||
f, err := os.CreateTemp(t.TempDir(), "webhook_url")
|
||||
require.NoError(t, err, "creating temp file failed")
|
||||
_, err = f.WriteString(u.String() + "\n")
|
||||
require.NoError(t, err, "writing to temp file failed")
|
||||
|
||||
notifier, err := New(
|
||||
&config.WebhookConfig{
|
||||
URLFile: f.Name(),
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
},
|
||||
test.CreateTmpl(t),
|
||||
promslog.NewNopLogger(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
test.AssertNotifyLeaksNoSecret(ctx, t, notifier, u.String())
|
||||
}
|
||||
|
||||
func TestWebhookURLTemplating(t *testing.T) {
|
||||
var calledURL string
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
calledURL = r.URL.Path
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
url string
|
||||
groupLabels model.LabelSet
|
||||
alertLabels model.LabelSet
|
||||
expectError bool
|
||||
expectedErrMsg string
|
||||
expectedPath string
|
||||
}{
|
||||
{
|
||||
name: "templating with alert labels",
|
||||
url: srv.URL + "/{{ .GroupLabels.alertname }}/{{ .CommonLabels.severity }}",
|
||||
groupLabels: model.LabelSet{"alertname": "TestAlert"},
|
||||
alertLabels: model.LabelSet{"alertname": "TestAlert", "severity": "critical"},
|
||||
expectError: false,
|
||||
expectedPath: "/TestAlert/critical",
|
||||
},
|
||||
{
|
||||
name: "invalid template field",
|
||||
url: srv.URL + "/{{ .InvalidField }}",
|
||||
groupLabels: model.LabelSet{"alertname": "TestAlert"},
|
||||
alertLabels: model.LabelSet{"alertname": "TestAlert"},
|
||||
expectError: true,
|
||||
expectedErrMsg: "failed to template webhook URL",
|
||||
},
|
||||
{
|
||||
name: "template renders to empty string",
|
||||
url: "{{ if .CommonLabels.nonexistent }}http://example.com{{ end }}",
|
||||
groupLabels: model.LabelSet{"alertname": "TestAlert"},
|
||||
alertLabels: model.LabelSet{"alertname": "TestAlert"},
|
||||
expectError: true,
|
||||
expectedErrMsg: "webhook URL is empty after templating",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
calledURL = "" // Reset for each test
|
||||
|
||||
notifier, err := New(
|
||||
&config.WebhookConfig{
|
||||
URL: config.SecretTemplateURL(tc.url),
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
},
|
||||
test.CreateTmpl(t),
|
||||
promslog.NewNopLogger(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = notify.WithGroupKey(ctx, "test-group")
|
||||
if tc.groupLabels != nil {
|
||||
ctx = notify.WithGroupLabels(ctx, tc.groupLabels)
|
||||
}
|
||||
|
||||
alerts := []*types.Alert{
|
||||
{
|
||||
Alert: model.Alert{
|
||||
Labels: tc.alertLabels,
|
||||
StartsAt: time.Now(),
|
||||
EndsAt: time.Now().Add(time.Hour),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err = notifier.Notify(ctx, alerts...)
|
||||
|
||||
if tc.expectError {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tc.expectedErrMsg)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expectedPath, calledURL)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,254 +0,0 @@
|
||||
package alertmanagertemplate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/types/ruletypes"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
// AlertManagerTemplater processes alert notification templates.
|
||||
type AlertManagerTemplater interface {
|
||||
// ProcessTemplates expands the title and body templates from input
|
||||
// against the provided alerts and returns the expanded templates.
|
||||
ProcessTemplates(ctx context.Context, input TemplateInput, alerts []*types.Alert) (*ExpandedTemplates, error)
|
||||
}
|
||||
|
||||
type alertManagerTemplater struct {
|
||||
tmpl *template.Template
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func New(tmpl *template.Template, logger *slog.Logger) AlertManagerTemplater {
|
||||
return &alertManagerTemplater{tmpl: tmpl, logger: logger}
|
||||
}
|
||||
|
||||
// ProcessTemplates expands the title and body templates from input
|
||||
// against the provided alerts and returns the expanded templates.
|
||||
func (at *alertManagerTemplater) ProcessTemplates(
|
||||
ctx context.Context,
|
||||
input TemplateInput,
|
||||
alerts []*types.Alert,
|
||||
) (*ExpandedTemplates, error) {
|
||||
ntd := at.buildNotificationTemplateData(ctx, alerts)
|
||||
|
||||
title, titleMissingVars, err := at.expandTitle(ctx, input, alerts, ntd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body, bodyMissingVars, err := at.expandBody(ctx, input, alerts, ntd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
missingVars := make(map[string]bool)
|
||||
for k := range titleMissingVars {
|
||||
missingVars[k] = true
|
||||
}
|
||||
for k := range bodyMissingVars {
|
||||
missingVars[k] = true
|
||||
}
|
||||
|
||||
return &ExpandedTemplates{Title: title, Body: body, MissingVars: missingVars}, nil
|
||||
}
|
||||
|
||||
// expandTitle expands the title template. Falls back to the default if the custom template
|
||||
// result in empty string.
|
||||
func (at *alertManagerTemplater) expandTitle(
|
||||
ctx context.Context,
|
||||
input TemplateInput,
|
||||
alerts []*types.Alert,
|
||||
ntd *NotificationTemplateData,
|
||||
) (string, map[string]bool, error) {
|
||||
if input.TitleTemplate != "" {
|
||||
processRes, err := PreProcessTemplateAndData(input.TitleTemplate, ntd)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
result, err := at.tmpl.ExecuteTextString(processRes.Template, processRes.Data)
|
||||
if err != nil {
|
||||
return "", nil, errors.NewInternalf(errors.CodeInvalidInput, "failed to execute template: %s", err.Error())
|
||||
}
|
||||
if strings.TrimSpace(result) != "" {
|
||||
return result, processRes.UnknownVars, nil
|
||||
}
|
||||
}
|
||||
|
||||
if input.DefaultTitleTemplate == "" {
|
||||
return "", nil, nil
|
||||
}
|
||||
// Fall back to the default title template if present in the input
|
||||
data := notify.GetTemplateData(ctx, at.tmpl, alerts, at.logger)
|
||||
result, err := at.tmpl.ExecuteTextString(input.DefaultTitleTemplate, data)
|
||||
return result, nil, err
|
||||
}
|
||||
|
||||
// expandBody expands the body template once per alert and concatenates the results to return resulting body template
|
||||
// it falls back to the default templates if body template is empty or result in empty string.
|
||||
func (at *alertManagerTemplater) expandBody(
|
||||
ctx context.Context,
|
||||
input TemplateInput,
|
||||
alerts []*types.Alert,
|
||||
ntd *NotificationTemplateData,
|
||||
) (string, map[string]bool, error) {
|
||||
if input.BodyTemplate != "" {
|
||||
var sb strings.Builder
|
||||
missingVars := make(map[string]bool)
|
||||
for i := range ntd.Alerts {
|
||||
processRes, err := PreProcessTemplateAndData(input.BodyTemplate, &ntd.Alerts[i])
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
for k := range processRes.UnknownVars {
|
||||
missingVars[k] = true
|
||||
}
|
||||
part, err := at.tmpl.ExecuteTextString(processRes.Template, processRes.Data)
|
||||
if err != nil {
|
||||
return "", nil, errors.NewInternalf(errors.CodeInvalidInput, "failed to execute template: %s", err.Error())
|
||||
}
|
||||
sb.WriteString(part)
|
||||
// Add separator if not last alert
|
||||
if i < len(ntd.Alerts)-1 {
|
||||
sb.WriteString("<br><br>")
|
||||
}
|
||||
}
|
||||
result := sb.String()
|
||||
if strings.TrimSpace(result) != "" {
|
||||
return result, missingVars, nil
|
||||
}
|
||||
}
|
||||
|
||||
if input.DefaultBodyTemplate == "" {
|
||||
return "", nil, nil
|
||||
}
|
||||
// Fall back to the default body template if present in the input
|
||||
data := notify.GetTemplateData(ctx, at.tmpl, alerts, at.logger)
|
||||
result, err := at.tmpl.ExecuteTextString(input.DefaultBodyTemplate, data)
|
||||
return result, nil, err
|
||||
}
|
||||
|
||||
// buildNotificationTemplateData creates the NotificationTemplateData using
|
||||
// info from context and the raw alerts.
|
||||
func (at *alertManagerTemplater) buildNotificationTemplateData(
|
||||
ctx context.Context,
|
||||
alerts []*types.Alert,
|
||||
) *NotificationTemplateData {
|
||||
// extract the required data from the context
|
||||
receiver, ok := notify.ReceiverName(ctx)
|
||||
if !ok {
|
||||
at.logger.WarnContext(ctx, "missing receiver name in context")
|
||||
}
|
||||
|
||||
groupLabels, ok := notify.GroupLabels(ctx)
|
||||
if !ok {
|
||||
at.logger.WarnContext(ctx, "missing group labels in context")
|
||||
}
|
||||
|
||||
// extract the external URL from the template
|
||||
externalURL := ""
|
||||
if at.tmpl.ExternalURL != nil {
|
||||
externalURL = at.tmpl.ExternalURL.String()
|
||||
}
|
||||
|
||||
commonAnnotations := extractCommonKV(alerts, func(a *types.Alert) model.LabelSet { return a.Annotations })
|
||||
commonLabels := extractCommonKV(alerts, func(a *types.Alert) model.LabelSet { return a.Labels })
|
||||
|
||||
// aggregate labels and annotations from all alerts
|
||||
labels := aggregateKV(alerts, func(a *types.Alert) model.LabelSet { return a.Labels })
|
||||
annotations := aggregateKV(alerts, func(a *types.Alert) model.LabelSet { return a.Annotations })
|
||||
|
||||
// build the alert data slice
|
||||
alertDataSlice := make([]AlertData, 0, len(alerts))
|
||||
for _, a := range alerts {
|
||||
ad := buildAlertData(a, receiver)
|
||||
alertDataSlice = append(alertDataSlice, ad)
|
||||
}
|
||||
|
||||
// count the number of firing and resolved alerts
|
||||
var firing, resolved int
|
||||
for _, ad := range alertDataSlice {
|
||||
if ad.IsFiring {
|
||||
firing++
|
||||
} else if ad.IsResolved {
|
||||
resolved++
|
||||
}
|
||||
}
|
||||
|
||||
// extract the rule-level convenience fields from common labels
|
||||
alertName := commonLabels[ruletypes.LabelAlertName]
|
||||
ruleID := commonLabels[ruletypes.LabelRuleId]
|
||||
ruleLink := commonLabels[ruletypes.LabelRuleSource]
|
||||
|
||||
// build the group labels
|
||||
gl := make(template.KV, len(groupLabels))
|
||||
for k, v := range groupLabels {
|
||||
gl[string(k)] = string(v)
|
||||
}
|
||||
|
||||
// build the notification template data
|
||||
return &NotificationTemplateData{
|
||||
Receiver: receiver,
|
||||
Status: string(types.Alerts(alerts...).Status()),
|
||||
AlertName: alertName,
|
||||
RuleID: ruleID,
|
||||
RuleLink: ruleLink,
|
||||
TotalFiring: firing,
|
||||
TotalResolved: resolved,
|
||||
Alerts: alertDataSlice,
|
||||
GroupLabels: gl,
|
||||
CommonLabels: commonLabels,
|
||||
CommonAnnotations: commonAnnotations,
|
||||
ExternalURL: externalURL,
|
||||
Labels: labels,
|
||||
Annotations: annotations,
|
||||
}
|
||||
}
|
||||
|
||||
// buildAlertData converts a single *types.Alert into an AlertData.
|
||||
func buildAlertData(a *types.Alert, receiver string) AlertData {
|
||||
labels := make(template.KV, len(a.Labels))
|
||||
for k, v := range a.Labels {
|
||||
labels[string(k)] = string(v)
|
||||
}
|
||||
|
||||
annotations := make(template.KV, len(a.Annotations))
|
||||
for k, v := range a.Annotations {
|
||||
annotations[string(k)] = string(v)
|
||||
}
|
||||
|
||||
status := string(a.Status())
|
||||
isFiring := a.Status() == model.AlertFiring
|
||||
isResolved := a.Status() == model.AlertResolved
|
||||
isMissingData := labels[ruletypes.LabelNoData] == "true"
|
||||
|
||||
return AlertData{
|
||||
Receiver: receiver,
|
||||
Status: status,
|
||||
Labels: labels,
|
||||
Annotations: annotations,
|
||||
StartsAt: a.StartsAt,
|
||||
EndsAt: a.EndsAt,
|
||||
GeneratorURL: a.GeneratorURL,
|
||||
Fingerprint: a.Fingerprint().String(),
|
||||
AlertName: labels[ruletypes.LabelAlertName],
|
||||
RuleID: labels[ruletypes.LabelRuleId],
|
||||
RuleLink: labels[ruletypes.LabelRuleSource],
|
||||
Severity: labels[ruletypes.LabelSeverityName],
|
||||
LogLink: annotations[ruletypes.AnnotationRelatedLogs],
|
||||
TraceLink: annotations[ruletypes.AnnotationRelatedTraces],
|
||||
Value: annotations[ruletypes.AnnotationValue],
|
||||
Threshold: annotations[ruletypes.AnnotationThreshold],
|
||||
CompareOp: annotations[ruletypes.AnnotationCompareOp],
|
||||
MatchType: annotations[ruletypes.AnnotationMatchType],
|
||||
IsFiring: isFiring,
|
||||
IsResolved: isResolved,
|
||||
IsMissingData: isMissingData,
|
||||
}
|
||||
}
|
||||
@@ -1,272 +0,0 @@
|
||||
package alertmanagertemplate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
test "github.com/SigNoz/signoz/pkg/alertmanager/alertmanagernotify/alertmanagernotifytest"
|
||||
"github.com/SigNoz/signoz/pkg/types/ruletypes"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
)
|
||||
|
||||
// testSetup returns an AlertTemplater and a context pre-populated with group key,
|
||||
// receiver name, and group labels for use in tests.
|
||||
func testSetup(t *testing.T) (AlertManagerTemplater, context.Context) {
|
||||
t.Helper()
|
||||
tmpl := test.CreateTmpl(t)
|
||||
ctx := context.Background()
|
||||
ctx = notify.WithGroupKey(ctx, "test-group")
|
||||
ctx = notify.WithReceiverName(ctx, "slack")
|
||||
ctx = notify.WithGroupLabels(ctx, model.LabelSet{"alertname": "TestAlert", "severity": "critical"})
|
||||
logger := slog.New(slog.DiscardHandler)
|
||||
return New(tmpl, logger), ctx
|
||||
}
|
||||
|
||||
func createAlert(labels, annotations map[string]string, isFiring bool) *types.Alert {
|
||||
ls := model.LabelSet{}
|
||||
for k, v := range labels {
|
||||
ls[model.LabelName(k)] = model.LabelValue(v)
|
||||
}
|
||||
ann := model.LabelSet{}
|
||||
for k, v := range annotations {
|
||||
ann[model.LabelName(k)] = model.LabelValue(v)
|
||||
}
|
||||
startsAt := time.Now()
|
||||
var endsAt time.Time
|
||||
if isFiring {
|
||||
endsAt = startsAt.Add(time.Hour)
|
||||
} else {
|
||||
startsAt = startsAt.Add(-2 * time.Hour)
|
||||
endsAt = startsAt.Add(-time.Hour)
|
||||
}
|
||||
return &types.Alert{Alert: model.Alert{Labels: ls, Annotations: ann, StartsAt: startsAt, EndsAt: endsAt}}
|
||||
}
|
||||
|
||||
func TestExpandTemplates(t *testing.T) {
|
||||
at, ctx := testSetup(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
alerts []*types.Alert
|
||||
input TemplateInput
|
||||
wantTitle string
|
||||
wantBody string
|
||||
wantMissingVars []string
|
||||
errorContains string
|
||||
}{
|
||||
{
|
||||
// High request throughput on a service — service is a custom label.
|
||||
// $labels.service extracts the label value; $annotations.description pulls the annotation.
|
||||
name: "new template: high request throughput for a service",
|
||||
alerts: []*types.Alert{
|
||||
createAlert(
|
||||
map[string]string{
|
||||
ruletypes.LabelAlertName: "HighRequestThroughput",
|
||||
ruletypes.LabelSeverityName: "warning",
|
||||
"service": "payment-service",
|
||||
},
|
||||
map[string]string{"description": "Request rate exceeded 10k/s"},
|
||||
true,
|
||||
),
|
||||
},
|
||||
input: TemplateInput{
|
||||
TitleTemplate: "High request throughput for $service",
|
||||
BodyTemplate: `The service $service is getting high request. Please investigate.
|
||||
Severity: $severity
|
||||
Status: $status
|
||||
Service: $service
|
||||
Description: $description`,
|
||||
},
|
||||
wantTitle: "High request throughput for payment-service",
|
||||
wantBody: `The service payment-service is getting high request. Please investigate.
|
||||
Severity: warning
|
||||
Status: firing
|
||||
Service: payment-service
|
||||
Description: Request rate exceeded 10k/s`,
|
||||
},
|
||||
{
|
||||
// Disk usage alert using old Go template syntax throughout.
|
||||
// No custom templates — both title and body use the default fallback path.
|
||||
name: "old template: disk usage high on database host",
|
||||
alerts: []*types.Alert{
|
||||
createAlert(
|
||||
map[string]string{ruletypes.LabelAlertName: "DiskUsageHigh",
|
||||
ruletypes.LabelSeverityName: "critical",
|
||||
"instance": "db-primary-01",
|
||||
},
|
||||
map[string]string{
|
||||
"summary": "Disk usage high on database host",
|
||||
"description": "Disk usage is high on the database host",
|
||||
"related_logs": "https://logs.example.com/search?q=DiskUsageHigh",
|
||||
"related_traces": "https://traces.example.com/search?q=DiskUsageHigh",
|
||||
},
|
||||
true,
|
||||
),
|
||||
},
|
||||
input: TemplateInput{
|
||||
DefaultTitleTemplate: `[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .CommonLabels.alertname }} for {{ .CommonLabels.job }}
|
||||
{{- if gt (len .CommonLabels) (len .GroupLabels) -}}
|
||||
{{" "}}(
|
||||
{{- with .CommonLabels.Remove .GroupLabels.Names }}
|
||||
{{- range $index, $label := .SortedPairs -}}
|
||||
{{ if $index }}, {{ end }}
|
||||
{{- $label.Name }}="{{ $label.Value -}}"
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
)
|
||||
{{- end }}`,
|
||||
DefaultBodyTemplate: `{{ range .Alerts -}}
|
||||
*Alert:* {{ .Labels.alertname }}{{ if .Labels.severity }} - {{ .Labels.severity }}{{ end }}
|
||||
|
||||
*Summary:* {{ .Annotations.summary }}
|
||||
*Description:* {{ .Annotations.description }}
|
||||
*RelatedLogs:* {{ if gt (len .Annotations.related_logs) 0 -}} View in <{{ .Annotations.related_logs }}|logs explorer> {{- end}}
|
||||
*RelatedTraces:* {{ if gt (len .Annotations.related_traces) 0 -}} View in <{{ .Annotations.related_traces }}|traces explorer> {{- end}}
|
||||
|
||||
*Details:*
|
||||
{{ range .Labels.SortedPairs }} • *{{ .Name }}:* {{ .Value }}
|
||||
{{ end }}
|
||||
{{ end }}`,
|
||||
},
|
||||
wantTitle: "[FIRING:1] DiskUsageHigh for (instance=\"db-primary-01\")",
|
||||
wantBody: `*Alert:* DiskUsageHigh - critical
|
||||
|
||||
*Summary:* Disk usage high on database host
|
||||
*Description:* Disk usage is high on the database host
|
||||
*RelatedLogs:* View in <https://logs.example.com/search?q=DiskUsageHigh|logs explorer>
|
||||
*RelatedTraces:* View in <https://traces.example.com/search?q=DiskUsageHigh|traces explorer>
|
||||
|
||||
*Details:*
|
||||
• *alertname:* DiskUsageHigh
|
||||
• *instance:* db-primary-01
|
||||
• *severity:* critical
|
||||
|
||||
`,
|
||||
},
|
||||
{
|
||||
// Pod crash loop on multiple pods — body is expanded once per alert
|
||||
// and joined with "<br><br>", with the pod name pulled from labels.
|
||||
name: "new template: pod crash loop on multiple pods, body per-alert",
|
||||
alerts: []*types.Alert{
|
||||
createAlert(map[string]string{ruletypes.LabelAlertName: "PodCrashLoop", "pod": "api-worker-1"}, nil, true),
|
||||
createAlert(map[string]string{ruletypes.LabelAlertName: "PodCrashLoop", "pod": "api-worker-2"}, nil, true),
|
||||
createAlert(map[string]string{ruletypes.LabelAlertName: "PodCrashLoop", "pod": "api-worker-3"}, nil, true),
|
||||
},
|
||||
input: TemplateInput{
|
||||
TitleTemplate: "$rule_name: $total_firing pods affected",
|
||||
BodyTemplate: "$labels.pod is crash looping",
|
||||
},
|
||||
wantTitle: "PodCrashLoop: 3 pods affected",
|
||||
wantBody: "api-worker-1 is crash looping<br><br>api-worker-2 is crash looping<br><br>api-worker-3 is crash looping",
|
||||
},
|
||||
{
|
||||
// Incident partially resolved — one service still down, one recovered.
|
||||
// Title shows the aggregate counts; body shows per-service status.
|
||||
name: "new template: service degradation with mixed firing and resolved alerts",
|
||||
alerts: []*types.Alert{
|
||||
createAlert(map[string]string{ruletypes.LabelAlertName: "ServiceDown", "service": "auth-service"}, nil, true),
|
||||
createAlert(map[string]string{ruletypes.LabelAlertName: "ServiceDown", "service": "payment-service"}, nil, false),
|
||||
},
|
||||
input: TemplateInput{
|
||||
TitleTemplate: "$total_firing firing, $total_resolved resolved",
|
||||
BodyTemplate: "$labels.service ($status)",
|
||||
},
|
||||
wantTitle: "1 firing, 1 resolved",
|
||||
wantBody: "auth-service (firing)<br><br>payment-service (resolved)",
|
||||
},
|
||||
{
|
||||
// $environment is not a known AlertData or NotificationTemplateData field,
|
||||
// so it lands in MissingVars and renders as "<no value>" in the output.
|
||||
name: "missing vars: unknown $environment variable in title",
|
||||
alerts: []*types.Alert{
|
||||
createAlert(map[string]string{ruletypes.LabelAlertName: "HighCPU", ruletypes.LabelSeverityName: "critical"}, nil, true),
|
||||
},
|
||||
input: TemplateInput{
|
||||
TitleTemplate: "[$environment] $rule_name",
|
||||
},
|
||||
wantTitle: "[<no value>] HighCPU",
|
||||
wantMissingVars: []string{"environment"},
|
||||
},
|
||||
{
|
||||
// $runbook_url is not a known field — someone tried to embed a runbook link
|
||||
// directly as a variable instead of via annotations.
|
||||
name: "missing vars: unknown $runbook_url variable in body",
|
||||
alerts: []*types.Alert{
|
||||
createAlert(map[string]string{ruletypes.LabelAlertName: "PodOOMKilled", ruletypes.LabelSeverityName: "warning"}, nil, true),
|
||||
},
|
||||
input: TemplateInput{
|
||||
BodyTemplate: "$rule_name: see runbook at $runbook_url",
|
||||
},
|
||||
wantBody: "PodOOMKilled: see runbook at <no value>",
|
||||
wantMissingVars: []string{"runbook_url"},
|
||||
},
|
||||
{
|
||||
// Both title and body use unknown variables; MissingVars is the union of both.
|
||||
name: "missing vars: unknown variables in both title and body",
|
||||
alerts: []*types.Alert{
|
||||
createAlert(map[string]string{ruletypes.LabelAlertName: "HighMemory", ruletypes.LabelSeverityName: "critical"}, nil, true),
|
||||
},
|
||||
input: TemplateInput{
|
||||
TitleTemplate: "[$environment] $rule_name and [{{ $service }}]",
|
||||
BodyTemplate: "$rule_name: see runbook at $runbook_url",
|
||||
},
|
||||
wantTitle: "[<no value>] HighMemory and [<no value>]",
|
||||
wantBody: "HighMemory: see runbook at <no value>",
|
||||
wantMissingVars: []string{"environment", "runbook_url", "service"},
|
||||
},
|
||||
{
|
||||
// Custom title template that expands to only whitespace triggers the fallback,
|
||||
// so the default title template is used instead.
|
||||
name: "fallback: whitespace-only custom title falls back to default",
|
||||
alerts: []*types.Alert{
|
||||
createAlert(map[string]string{ruletypes.LabelAlertName: "HighCPU", ruletypes.LabelSeverityName: "critical"}, nil, false),
|
||||
},
|
||||
input: TemplateInput{
|
||||
TitleTemplate: " ",
|
||||
DefaultTitleTemplate: "{{ .CommonLabels.alertname }} ({{ .Status | toUpper }})",
|
||||
BodyTemplate: "$rule_name ($severity) for $alertname",
|
||||
},
|
||||
wantTitle: "HighCPU (RESOLVED)",
|
||||
wantBody: "HighCPU (critical) for HighCPU",
|
||||
},
|
||||
{
|
||||
name: "using non-existing function in template",
|
||||
alerts: []*types.Alert{
|
||||
createAlert(map[string]string{ruletypes.LabelAlertName: "HighCPU", ruletypes.LabelSeverityName: "critical"}, nil, true),
|
||||
},
|
||||
input: TemplateInput{
|
||||
TitleTemplate: "$rule_name ({{$severity | toUpperAndTrim}}) for $alertname",
|
||||
},
|
||||
errorContains: "function \"toUpperAndTrim\" not defined",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := at.ProcessTemplates(ctx, tc.input, tc.alerts)
|
||||
if tc.errorContains != "" {
|
||||
require.ErrorContains(t, err, tc.errorContains)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
if tc.wantTitle != "" {
|
||||
require.Equal(t, tc.wantTitle, got.Title)
|
||||
}
|
||||
if tc.wantBody != "" {
|
||||
require.Equal(t, tc.wantBody, got.Body)
|
||||
}
|
||||
|
||||
require.Len(t, got.MissingVars, len(tc.wantMissingVars))
|
||||
for _, v := range tc.wantMissingVars {
|
||||
require.True(t, got.MissingVars[v], "expected %q in MissingVars", v)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,242 +0,0 @@
|
||||
package alertmanagertemplate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
)
|
||||
|
||||
// fieldMapping represents a mapping from a JSON tag name to its struct field name.
|
||||
type fieldMapping struct {
|
||||
VarName string // JSON tag name (e.g., "receiver", "rule_name")
|
||||
FieldName string // Struct field name (e.g., "Receiver", "AlertName")
|
||||
}
|
||||
|
||||
// extractFieldMappings uses reflection to extract field mappings from a struct.
|
||||
func extractFieldMappings(data any) []fieldMapping {
|
||||
val := reflect.ValueOf(data)
|
||||
// Handle pointer types
|
||||
if val.Kind() == reflect.Ptr {
|
||||
if val.IsNil() {
|
||||
return nil
|
||||
}
|
||||
val = val.Elem()
|
||||
}
|
||||
// return nil if the given data is not a struct
|
||||
if val.Kind() != reflect.Struct {
|
||||
return nil
|
||||
}
|
||||
typ := val.Type()
|
||||
|
||||
var mappings []fieldMapping
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
field := typ.Field(i)
|
||||
// Skip unexported fields
|
||||
if !field.IsExported() {
|
||||
continue
|
||||
}
|
||||
// Get JSON tag name
|
||||
jsonTag := field.Tag.Get("json")
|
||||
if jsonTag == "" || jsonTag == "-" {
|
||||
continue
|
||||
}
|
||||
// Extract the name part (before any comma options like omitempty)
|
||||
varName := strings.Split(jsonTag, ",")[0]
|
||||
if varName == "" {
|
||||
continue
|
||||
}
|
||||
varFieldName := field.Tag.Get("mapstructure")
|
||||
if varFieldName == "" {
|
||||
varFieldName = field.Name
|
||||
}
|
||||
// Skip complex types: slices and interfaces
|
||||
kind := field.Type.Kind()
|
||||
if kind == reflect.Slice || kind == reflect.Interface {
|
||||
continue
|
||||
}
|
||||
// For struct types, we skip all but with few exceptions like time.Time
|
||||
if kind == reflect.Struct {
|
||||
// Allow time.Time which is commonly used
|
||||
if field.Type.String() != "time.Time" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
mappings = append(mappings, fieldMapping{
|
||||
VarName: varName,
|
||||
FieldName: varFieldName,
|
||||
})
|
||||
}
|
||||
|
||||
return mappings
|
||||
}
|
||||
|
||||
// extractNestedFieldsDefinitions adds the labels and annotations keys from the data struct to the template variable definitions
|
||||
// it takes the known data struct and extracts the labels and annotations maps and adds their keys to template variable definitions to be used in the template
|
||||
func extractNestedFieldsDefinitions(data any) map[string]string {
|
||||
variables := make(map[string]string)
|
||||
|
||||
addLabelsAndAnnotations := func(labels, annotations map[string]string) {
|
||||
for k := range annotations {
|
||||
variables[k] = fmt.Sprintf("index .annotations \"%s\"", k)
|
||||
}
|
||||
for k := range labels {
|
||||
variables[k] = fmt.Sprintf("index .labels \"%s\"", k)
|
||||
}
|
||||
}
|
||||
|
||||
switch data := data.(type) {
|
||||
case *NotificationTemplateData:
|
||||
addLabelsAndAnnotations(data.Labels, data.Annotations)
|
||||
case *AlertData:
|
||||
addLabelsAndAnnotations(data.Labels, data.Annotations)
|
||||
default:
|
||||
return variables
|
||||
}
|
||||
|
||||
return variables
|
||||
}
|
||||
|
||||
// prepareDataForTemplating prepares the data for templating by adding the labels and annotations values to the resulting map
|
||||
// so they can be accessed directly from root level, the predefined values takes precedence over the labels and annotations values
|
||||
// for example, if labels has a value called rule_name, which collides with the rule_name field in the data struct, the value from the data struct will take precedence
|
||||
func prepareDataForTemplating(data any) (map[string]interface{}, error) {
|
||||
var result map[string]interface{}
|
||||
if err := mapstructure.Decode(data, &result); err != nil {
|
||||
return nil, errors.WrapInvalidInputf(err, errors.CodeInvalidInput, "failed to prepare data for templating")
|
||||
}
|
||||
|
||||
addLabelsAndAnnotationsValues := func(labels, annotations map[string]string) {
|
||||
for k, v := range labels {
|
||||
if _, ok := result[k]; !ok {
|
||||
result[k] = v
|
||||
}
|
||||
}
|
||||
for k, v := range annotations {
|
||||
if _, ok := result[k]; !ok {
|
||||
result[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch data := data.(type) {
|
||||
case *NotificationTemplateData:
|
||||
addLabelsAndAnnotationsValues(data.Labels, data.Annotations)
|
||||
case *AlertData:
|
||||
addLabelsAndAnnotationsValues(data.Labels, data.Annotations)
|
||||
default:
|
||||
return result, nil
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// generateVariableDefinitions creates `{{ $varname := "" }}` declarations for each variable name.
|
||||
func generateVariableDefinitions(varNames map[string]string) string {
|
||||
if len(varNames) == 0 {
|
||||
return ""
|
||||
}
|
||||
var sb strings.Builder
|
||||
for name := range varNames {
|
||||
fmt.Fprintf(&sb, `{{ $%s := %s }}`, name, varNames[name])
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// buildVariableDefinitions constructs the full variable definition preamble for a template.
|
||||
// containing all known and unknown variables, the reason to include unknown variables is to
|
||||
// populate them with "<no value>" in template so go-text-template don't throw errors
|
||||
// when these variables are used in the template.
|
||||
func buildVariableDefinitions(tmpl string, data any) (string, map[string]bool, error) {
|
||||
// Extract the initial fields from the data struct and add to the definitions
|
||||
mappings := extractFieldMappings(data)
|
||||
|
||||
// Add variables from struct root level fields to the definitions
|
||||
variables := make(map[string]string)
|
||||
for _, m := range mappings {
|
||||
variables[m.VarName] = fmt.Sprintf(".%s", m.FieldName)
|
||||
}
|
||||
|
||||
// Extract the nested fields definitions from the data struct, like labels, annotations, etc.
|
||||
// once extracted we add them to the variables map along with the field address
|
||||
nestedVariables := extractNestedFieldsDefinitions(data)
|
||||
for k, v := range nestedVariables {
|
||||
variables[k] = v
|
||||
}
|
||||
|
||||
// variables that are used throughout the template
|
||||
usedVars, err := ExtractUsedVariables(tmpl)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
// Compute unknown variables: used in template but not covered by a field mapping
|
||||
probableUnknownVars := make(map[string]bool)
|
||||
for name := range usedVars {
|
||||
_, ok := variables[name]
|
||||
if !ok {
|
||||
probableUnknownVars[name] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Add missing variables to the definitions with "<no value>"
|
||||
// missingkey=zero is used to replace the missing value with "<no value>"
|
||||
// but it only works when getting map values like {{ .keyfrommap }} from map and in struct this breaks
|
||||
// with missing variable errors, we add missing variables in map so when directly variables
|
||||
// are accessed directly in template block like {{ $variable }} it's handled and doesn't throw errors.
|
||||
for name := range probableUnknownVars {
|
||||
variables[name] = `"<no value>"`
|
||||
}
|
||||
|
||||
return generateVariableDefinitions(variables), probableUnknownVars, nil
|
||||
}
|
||||
|
||||
type ProcessingResult struct {
|
||||
Template string
|
||||
Data map[string]interface{}
|
||||
// UnknownVars is the set of possible unknown variables exptracted using regex
|
||||
UnknownVars map[string]bool
|
||||
}
|
||||
|
||||
// PreProcessTemplateAndData prepares a template string and struct data for Go template execution.
|
||||
//
|
||||
// Input: "$receiver has $rule_name in $status state"
|
||||
// Output: "{{ $receiver := .Receiver }}...{{ $receiver }} has {{ $rule_name }} in {{ $status }} state"
|
||||
func PreProcessTemplateAndData(tmpl string, data any) (*ProcessingResult, error) {
|
||||
// Handle empty template
|
||||
unknownVars := make(map[string]bool)
|
||||
if tmpl == "" {
|
||||
result, err := prepareDataForTemplating(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ProcessingResult{Data: result, UnknownVars: unknownVars}, nil
|
||||
}
|
||||
|
||||
// Build variable definitions: known struct fields + fallback empty-string declarations
|
||||
definitions, unknownVars, err := buildVariableDefinitions(tmpl, data)
|
||||
if err != nil {
|
||||
return nil, errors.WrapInvalidInputf(err, errors.CodeInvalidInput, "failed to build template definitions")
|
||||
}
|
||||
|
||||
// Attach definitions prefix so WrapDollarVariables can parse the AST without "undefined variable" errors.
|
||||
finalTmpl := definitions + tmpl
|
||||
|
||||
// Call WrapDollarVariables to transform bare $variable references to go-text-template format
|
||||
// with {{ $variable }} syntax from $variable syntax
|
||||
wrappedTmpl, err := WrapDollarVariables(finalTmpl)
|
||||
if err != nil {
|
||||
return nil, errors.WrapInvalidInputf(err, errors.CodeInvalidInput, "failed to prepare template for templating")
|
||||
}
|
||||
|
||||
// Convert struct to map using mapstructure to be used for template execution
|
||||
result, err := prepareDataForTemplating(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ProcessingResult{Template: wrappedTmpl, Data: result, UnknownVars: unknownVars}, nil
|
||||
}
|
||||
@@ -1,316 +0,0 @@
|
||||
package alertmanagertemplate
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestExtractFieldMappings(t *testing.T) {
|
||||
// Struct with various field types to test extraction logic
|
||||
type TestStruct struct {
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
ActiveUserCount int `json:"user_count" mapstructure:"active_user_count"`
|
||||
IsActive bool `json:"is_active"`
|
||||
CreatedAt time.Time `json:"created_at"` // time.Time allowed
|
||||
Items []string `json:"items"` // slice skipped
|
||||
unexported string // unexported skipped (no tag needed)
|
||||
NoTag string // no json tag skipped
|
||||
SkippedTag string `json:"-"` // json:"-" skipped
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
data any
|
||||
expected []fieldMapping
|
||||
}{
|
||||
{
|
||||
name: "struct with mixed field types",
|
||||
data: TestStruct{Name: "test", ActiveUserCount: 5, unexported: ""},
|
||||
expected: []fieldMapping{
|
||||
{VarName: "name", FieldName: "Name"},
|
||||
{VarName: "status", FieldName: "Status"},
|
||||
{VarName: "user_count", FieldName: "active_user_count"},
|
||||
{VarName: "is_active", FieldName: "IsActive"},
|
||||
{VarName: "created_at", FieldName: "CreatedAt"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nil data",
|
||||
data: nil,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "non-struct type",
|
||||
data: "string",
|
||||
expected: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := extractFieldMappings(tc.data)
|
||||
require.Equal(t, tc.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildVariableDefinitions(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
tmpl string
|
||||
data any
|
||||
expectedVars []string // substrings that must appear in result
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "empty template still returns struct field definitions",
|
||||
tmpl: "",
|
||||
data: &NotificationTemplateData{Receiver: "test"},
|
||||
expectedVars: []string{
|
||||
"{{ $receiver := .receiver }}",
|
||||
"{{ $status := .status }}",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mix of known and unknown vars",
|
||||
tmpl: "$rule_name: $custom_label",
|
||||
data: &AlertData{AlertName: "test", Status: "ok", Severity: "critical"},
|
||||
expectedVars: []string{
|
||||
"{{ $rule_name := .rule_name }}",
|
||||
"{{ $status := .status }}",
|
||||
"{{ $severity := .severity }}",
|
||||
`{{ $custom_label := "<no value>" }}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nested fields definitions coming from NotificationTemplateData",
|
||||
tmpl: "$severity for $service",
|
||||
data: &NotificationTemplateData{Labels: template.KV{
|
||||
"severity": "critical",
|
||||
"service": "test",
|
||||
}},
|
||||
expectedVars: []string{
|
||||
"{{ $severity := index .labels \"severity\" }}",
|
||||
"{{ $service := index .labels \"service\" }}",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nested fields definitions coming from AlertData",
|
||||
tmpl: "$severity for $service",
|
||||
data: &AlertData{Labels: template.KV{
|
||||
"severity": "critical",
|
||||
"service": "test",
|
||||
}},
|
||||
expectedVars: []string{
|
||||
"{{ $severity := index .labels \"severity\" }}",
|
||||
"{{ $service := index .labels \"service\" }}",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid template syntax returns error",
|
||||
tmpl: "{{invalid",
|
||||
data: &NotificationTemplateData{},
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result, _, err := buildVariableDefinitions(tc.tmpl, tc.data)
|
||||
if tc.expectError {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
if len(tc.expectedVars) == 0 {
|
||||
require.Empty(t, result)
|
||||
return
|
||||
}
|
||||
for _, expected := range tc.expectedVars {
|
||||
require.Contains(t, result, expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreProcessTemplateAndData(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
tmpl string
|
||||
data any
|
||||
expectedTemplateContains []string
|
||||
expectedData map[string]any
|
||||
expectedUnknownVars map[string]bool
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "NotificationTemplateData with dollar variables",
|
||||
tmpl: "[$status] $rule_name (ID: $rule_id) - Firing: $total_firing, Resolved: $total_resolved, Severity: $severity",
|
||||
data: &NotificationTemplateData{
|
||||
Receiver: "pagerduty",
|
||||
Status: "firing",
|
||||
AlertName: "HighMemory",
|
||||
RuleID: "rule-123",
|
||||
Labels: template.KV{
|
||||
"severity": "critical",
|
||||
},
|
||||
TotalFiring: 3,
|
||||
TotalResolved: 1,
|
||||
},
|
||||
expectedTemplateContains: []string{
|
||||
"{{$status := .status}}",
|
||||
"{{$rule_name := .rule_name}}",
|
||||
"{{$rule_id := .rule_id}}",
|
||||
"{{$total_firing := .total_firing}}",
|
||||
"{{$total_resolved := .total_resolved}}",
|
||||
"{{$severity := index .labels \"severity\"}}",
|
||||
"[{{ .status }}] {{ .rule_name }} (ID: {{ .rule_id }}) - Firing: {{ .total_firing }}, Resolved: {{ .total_resolved }}",
|
||||
},
|
||||
expectedData: map[string]any{
|
||||
"status": "firing",
|
||||
"rule_name": "HighMemory",
|
||||
"rule_id": "rule-123",
|
||||
"total_firing": 3,
|
||||
"total_resolved": 1,
|
||||
"severity": "critical",
|
||||
},
|
||||
expectedUnknownVars: map[string]bool{},
|
||||
},
|
||||
{
|
||||
name: "AlertData with dollar variables",
|
||||
tmpl: "$rule_name: Value $value exceeded $threshold (Status: $status, Severity: $severity, Description: $description)",
|
||||
data: &AlertData{
|
||||
Receiver: "webhook",
|
||||
Status: "resolved",
|
||||
AlertName: "DiskFull",
|
||||
RuleID: "disk-001",
|
||||
Severity: "warning",
|
||||
Annotations: template.KV{
|
||||
"description": "Disk full and cannot be written to",
|
||||
},
|
||||
Value: "85%",
|
||||
Threshold: "80%",
|
||||
IsFiring: false,
|
||||
IsResolved: true,
|
||||
},
|
||||
expectedTemplateContains: []string{
|
||||
"{{$rule_name := .rule_name}}",
|
||||
"{{$value := .value}}",
|
||||
"{{$threshold := .threshold}}",
|
||||
"{{$status := .status}}",
|
||||
"{{$severity := .severity}}",
|
||||
"{{$description := index .annotations \"description\"}}",
|
||||
"{{ .rule_name }}: Value {{ .value }} exceeded {{ .threshold }} (Status: {{ .status }}, Severity: {{ .severity }}, Description: {{ .description }})",
|
||||
},
|
||||
expectedData: map[string]any{
|
||||
"status": "resolved",
|
||||
"rule_name": "DiskFull",
|
||||
"rule_id": "disk-001",
|
||||
"severity": "warning",
|
||||
"value": "85%",
|
||||
"threshold": "80%",
|
||||
"description": "Disk full and cannot be written to",
|
||||
},
|
||||
expectedUnknownVars: map[string]bool{},
|
||||
},
|
||||
{
|
||||
name: "mixed dollar and dot notation with both labels and annotations",
|
||||
tmpl: "Alert $rule_name has {{.total_firing}} firing alerts",
|
||||
data: &NotificationTemplateData{
|
||||
AlertName: "HighCPU",
|
||||
TotalFiring: 5,
|
||||
Labels: template.KV{
|
||||
"value": "<MASKED VALUE>",
|
||||
},
|
||||
Annotations: template.KV{
|
||||
"value": "85%",
|
||||
},
|
||||
},
|
||||
expectedTemplateContains: []string{
|
||||
"{{$rule_name := .rule_name}}",
|
||||
"{{$value := index .labels \"value\"}}",
|
||||
"Alert {{ .rule_name }} has {{.total_firing}} firing alerts",
|
||||
},
|
||||
expectedData: map[string]any{
|
||||
"rule_name": "HighCPU",
|
||||
"total_firing": 5,
|
||||
"value": "<MASKED VALUE>",
|
||||
},
|
||||
expectedUnknownVars: map[string]bool{},
|
||||
},
|
||||
{
|
||||
name: "empty template",
|
||||
tmpl: "",
|
||||
data: &NotificationTemplateData{Receiver: "slack"},
|
||||
},
|
||||
{
|
||||
name: "invalid template syntax",
|
||||
tmpl: "{{invalid",
|
||||
data: &NotificationTemplateData{},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "unknown dollar var in text renders empty",
|
||||
tmpl: "alert $custom_note fired",
|
||||
data: &NotificationTemplateData{AlertName: "HighCPU"},
|
||||
expectedTemplateContains: []string{
|
||||
`{{$custom_note := "<no value>"}}`,
|
||||
"alert {{ .custom_note }} fired",
|
||||
},
|
||||
expectedUnknownVars: map[string]bool{"custom_note": true},
|
||||
},
|
||||
{
|
||||
name: "unknown dollar var in action block renders empty",
|
||||
tmpl: "alert {{ $custom_note }} fired",
|
||||
data: &NotificationTemplateData{AlertName: "HighCPU"},
|
||||
expectedTemplateContains: []string{
|
||||
`{{$custom_note := "<no value>"}}`,
|
||||
`alert {{$custom_note}} fired`,
|
||||
},
|
||||
expectedUnknownVars: map[string]bool{"custom_note": true},
|
||||
},
|
||||
{
|
||||
name: "mix of known and unknown vars",
|
||||
tmpl: "$rule_name: $custom_label",
|
||||
data: &NotificationTemplateData{AlertName: "HighCPU"},
|
||||
expectedTemplateContains: []string{
|
||||
"{{$rule_name := .rule_name}}",
|
||||
`{{$custom_label := "<no value>"}}`,
|
||||
"{{ .rule_name }}: {{ .custom_label }}",
|
||||
},
|
||||
expectedData: map[string]any{"rule_name": "HighCPU"},
|
||||
expectedUnknownVars: map[string]bool{"custom_label": true},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result, err := PreProcessTemplateAndData(tc.tmpl, tc.data)
|
||||
|
||||
if tc.expectError {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
if tc.tmpl == "" {
|
||||
require.Equal(t, "", result.Template)
|
||||
return
|
||||
}
|
||||
|
||||
for _, substr := range tc.expectedTemplateContains {
|
||||
require.Contains(t, result.Template, substr)
|
||||
}
|
||||
for k, v := range tc.expectedData {
|
||||
require.Equal(t, v, result.Data[k])
|
||||
}
|
||||
if tc.expectedUnknownVars != nil {
|
||||
require.Equal(t, tc.expectedUnknownVars, result.UnknownVars)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
package alertmanagertemplate
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
)
|
||||
|
||||
// TemplateInput carries the title/body templates
|
||||
// and their defaults to apply in case the custom templates
|
||||
// are result in empty strings.
|
||||
type TemplateInput struct {
|
||||
TitleTemplate string
|
||||
BodyTemplate string
|
||||
DefaultTitleTemplate string
|
||||
DefaultBodyTemplate string
|
||||
}
|
||||
|
||||
// ExpandedTemplates is the result of ExpandTemplates.
|
||||
type ExpandedTemplates struct {
|
||||
Title string
|
||||
Body string
|
||||
MissingVars map[string]bool // union of unknown vars from title + body templates
|
||||
}
|
||||
|
||||
// AlertData holds per-alert data used when expanding body templates
|
||||
type AlertData struct {
|
||||
Receiver string `json:"receiver" mapstructure:"receiver"`
|
||||
Status string `json:"status" mapstructure:"status"`
|
||||
Labels template.KV `json:"labels" mapstructure:"labels"`
|
||||
Annotations template.KV `json:"annotations" mapstructure:"annotations"`
|
||||
StartsAt time.Time `json:"starts_at" mapstructure:"starts_at"`
|
||||
EndsAt time.Time `json:"ends_at" mapstructure:"ends_at"`
|
||||
GeneratorURL string `json:"generator_url" mapstructure:"generator_url"`
|
||||
Fingerprint string `json:"fingerprint" mapstructure:"fingerprint"`
|
||||
|
||||
// Convenience fields extracted from well-known labels/annotations.
|
||||
AlertName string `json:"rule_name" mapstructure:"rule_name"`
|
||||
RuleID string `json:"rule_id" mapstructure:"rule_id"`
|
||||
RuleLink string `json:"rule_link" mapstructure:"rule_link"`
|
||||
Severity string `json:"severity" mapstructure:"severity"`
|
||||
|
||||
// Alert internal data fields
|
||||
Value string `json:"value" mapstructure:"value"`
|
||||
Threshold string `json:"threshold" mapstructure:"threshold"`
|
||||
CompareOp string `json:"compare_op" mapstructure:"compare_op"`
|
||||
MatchType string `json:"match_type" mapstructure:"match_type"`
|
||||
|
||||
// Link annotations added by the rule evaluator.
|
||||
LogLink string `json:"log_link" mapstructure:"log_link"`
|
||||
TraceLink string `json:"trace_link" mapstructure:"trace_link"`
|
||||
|
||||
// Status booleans for easy conditional templating.
|
||||
IsFiring bool `json:"is_firing" mapstructure:"is_firing"`
|
||||
IsResolved bool `json:"is_resolved" mapstructure:"is_resolved"`
|
||||
IsMissingData bool `json:"is_missing_data" mapstructure:"is_missing_data"`
|
||||
IsRecovering bool `json:"is_recovering" mapstructure:"is_recovering"`
|
||||
}
|
||||
|
||||
// NotificationTemplateData is the top-level data struct provided to custom templates.
|
||||
type NotificationTemplateData struct {
|
||||
Receiver string `json:"receiver" mapstructure:"receiver"`
|
||||
Status string `json:"status" mapstructure:"status"`
|
||||
|
||||
// Convenience fields for title templates.
|
||||
AlertName string `json:"rule_name" mapstructure:"rule_name"`
|
||||
RuleID string `json:"rule_id" mapstructure:"rule_id"`
|
||||
RuleLink string `json:"rule_link" mapstructure:"rule_link"`
|
||||
TotalFiring int `json:"total_firing" mapstructure:"total_firing"`
|
||||
TotalResolved int `json:"total_resolved" mapstructure:"total_resolved"`
|
||||
|
||||
// Per-alert data, also available as filtered sub-slices.
|
||||
Alerts []AlertData `json:"-" mapstructure:"-"`
|
||||
|
||||
// Cross-alert aggregates, computed as intersection across all alerts.
|
||||
GroupLabels template.KV `json:"group_labels" mapstructure:"group_labels"`
|
||||
CommonLabels template.KV `json:"common_labels" mapstructure:"common_labels"`
|
||||
CommonAnnotations template.KV `json:"common_annotations" mapstructure:"common_annotations"`
|
||||
ExternalURL string `json:"external_url" mapstructure:"external_url"`
|
||||
// Labels and Annotations that are collection of labels
|
||||
// and annotations from all alerts, it includes only the common labels and annotations
|
||||
// and for non-common labels and annotations, it picks some first few labels/annotations
|
||||
// and joins them with ", " to avoid blank values in the template
|
||||
Labels template.KV `json:"labels" mapstructure:"labels"`
|
||||
Annotations template.KV `json:"annotations" mapstructure:"annotations"`
|
||||
}
|
||||
@@ -1,234 +0,0 @@
|
||||
package alertmanagertemplate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/template/parse"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/types/alertmanagertypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/ruletypes"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
// maxAggregatedValues is the maximum number of unique values to include
|
||||
// when aggregating non-common label/annotation values across alerts.
|
||||
const maxAggregatedValues = 5
|
||||
|
||||
// bareVariableRegex matches bare $variable references including dotted paths like $service.name.
|
||||
var bareVariableRegex = regexp.MustCompile(`\$(\w+(?:\.\w+)*)`)
|
||||
|
||||
// bareVariableRegexFirstSeg matches only the base $variable name, stopping before any dotted path.
|
||||
// e.g. "$labels.severity" matches "$labels", "$name" matches "$name".
|
||||
var bareVariableRegexFirstSeg = regexp.MustCompile(`\$\w+`)
|
||||
|
||||
// ExtractTemplatesFromAnnotations computes the common annotations across all alerts
|
||||
// and returns the values for the title_template and body_template annotation keys as title and body templates.
|
||||
func ExtractTemplatesFromAnnotations(alerts []*types.Alert) (titleTemplate, bodyTemplate string) {
|
||||
if len(alerts) == 0 {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
commonAnnotations := extractCommonKV(alerts, func(a *types.Alert) model.LabelSet { return a.Annotations })
|
||||
return commonAnnotations[ruletypes.AnnotationTitleTemplate], commonAnnotations[ruletypes.AnnotationBodyTemplate]
|
||||
}
|
||||
|
||||
// WrapDollarVariables wraps bare $variable references in Go template syntax.
|
||||
// Example transformations:
|
||||
// - "$name is $status" -> "{{ $name }} is {{ $status }}"
|
||||
// - "$labels.severity" -> "{{ index .labels \"severity\" }}"
|
||||
// - "$labels.http.status" -> "{{ index .labels \"http.status\" }}"
|
||||
// - "$annotations.summary" -> "{{ index .annotations \"summary\" }}"
|
||||
// - "$service.name" -> "{{ index . \"service.name\" }}"
|
||||
// - "$name is {{ .Status }}" -> "{{ $name }} is {{ .Status }}"
|
||||
func WrapDollarVariables(src string) (string, error) {
|
||||
if src == "" {
|
||||
return src, nil
|
||||
}
|
||||
|
||||
funcMap := alertmanagertypes.AdditionalFuncMap()
|
||||
// Create a new parse.Tree directly
|
||||
tree := parse.New("template")
|
||||
tree.Mode = parse.SkipFuncCheck
|
||||
|
||||
// Parse the template
|
||||
_, err := tree.Parse(src, "{{", "}}", make(map[string]*parse.Tree), funcMap)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Walk the AST and transform TextNodes
|
||||
walkAndWrapTextNodes(tree.Root)
|
||||
|
||||
// Return the reassembled template
|
||||
return tree.Root.String(), nil
|
||||
}
|
||||
|
||||
// walkAndWrapTextNodes recursively walks the parse tree trying to find a text node
|
||||
// once text node is found it wraps the bare $variable and changes it to index based
|
||||
// element access form datamap like .key or .key.subkey
|
||||
func walkAndWrapTextNodes(node parse.Node) {
|
||||
if reflect.ValueOf(node).IsNil() {
|
||||
return
|
||||
}
|
||||
|
||||
switch n := node.(type) {
|
||||
// `$name is {{.Status}}` is a list node with one text and one action node
|
||||
case *parse.ListNode:
|
||||
// Recurse into all child nodes
|
||||
if n.Nodes != nil {
|
||||
for _, child := range n.Nodes {
|
||||
walkAndWrapTextNodes(child)
|
||||
}
|
||||
}
|
||||
|
||||
// `$name is ` is a text node with plain text in root
|
||||
// we try to find the $name variable and wrap it with template block
|
||||
// like `{{ .name }}`, for labels and annotations we use the index to access the value
|
||||
// so `$labels.service` becomes `{{ index .labels "service" }}`
|
||||
case *parse.TextNode:
|
||||
// Transform $variable based on its pattern
|
||||
n.Text = bareVariableRegex.ReplaceAllFunc(n.Text, func(match []byte) []byte {
|
||||
// Extract variable name without the $
|
||||
varName := string(match[1:])
|
||||
|
||||
// Check if variable contains dots
|
||||
if strings.Contains(varName, ".") {
|
||||
// Check for reserved prefixes: labels.* or annotations.*
|
||||
if strings.HasPrefix(varName, "labels.") {
|
||||
key := strings.TrimPrefix(varName, "labels.")
|
||||
return []byte(fmt.Sprintf(`{{ index .labels "%s" }}`, key))
|
||||
}
|
||||
if strings.HasPrefix(varName, "annotations.") {
|
||||
key := strings.TrimPrefix(varName, "annotations.")
|
||||
return []byte(fmt.Sprintf(`{{ index .annotations "%s" }}`, key))
|
||||
}
|
||||
// Other dotted variables: index into root context
|
||||
return []byte(fmt.Sprintf(`{{ index . "%s" }}`, varName))
|
||||
}
|
||||
|
||||
// Simple variables: use dot notation to directly access the field
|
||||
// without raising any error due to missing variables
|
||||
return []byte(fmt.Sprintf("{{ .%s }}", varName))
|
||||
})
|
||||
|
||||
// `{{if pipeline}} T1 {{else}} T0 {{end}}` is a if node with T1 part of List and T0 part of ElseList
|
||||
case *parse.IfNode:
|
||||
// Recurse into both branches
|
||||
walkAndWrapTextNodes(n.List)
|
||||
walkAndWrapTextNodes(n.ElseList)
|
||||
|
||||
// `{{range pipeline}} T1 {{else}} T0 {{end}}` is a range node with T1 part of List and T0 part of ElseList
|
||||
case *parse.RangeNode:
|
||||
// Recurse into both branches
|
||||
walkAndWrapTextNodes(n.List)
|
||||
walkAndWrapTextNodes(n.ElseList)
|
||||
|
||||
// All other node types (ActionNode, PipeNode, VariableNode, etc.) are already
|
||||
// inside {{ }} action blocks and don't need transformation
|
||||
|
||||
// Support for `with` can be added later when we start supporting it in editor block
|
||||
}
|
||||
}
|
||||
|
||||
// ExtractUsedVariables returns the set of all $variable referenced in template
|
||||
// — text nodes, action blocks, branch conditions, and loop declarations — regardless of scope.
|
||||
// After finding all variables we find the ones which are not part of our alert data and handle them so
|
||||
// Go-text-template parser doesn't rejects undefined $variables
|
||||
func ExtractUsedVariables(src string) (map[string]bool, error) {
|
||||
if src == "" {
|
||||
return map[string]bool{}, nil
|
||||
}
|
||||
|
||||
// Regex-scan raw template string to collect all $var base names.
|
||||
// bareVariableRegexFirstSeg stops before dots, so "$labels.severity" yields "$labels".
|
||||
used := make(map[string]bool)
|
||||
for _, m := range bareVariableRegexFirstSeg.FindAll([]byte(src), -1) {
|
||||
used[string(m[1:])] = true // strip leading "$"
|
||||
}
|
||||
|
||||
// Build a preamble that pre-declares every found variable.
|
||||
// This prevents "undefined variable" parse errors for $vars used in action
|
||||
// blocks while still letting genuine syntax errors propagate.
|
||||
var preamble strings.Builder
|
||||
for name := range used {
|
||||
fmt.Fprintf(&preamble, `{{$%s := ""}}`, name)
|
||||
}
|
||||
|
||||
// Validate template syntax.
|
||||
funcMap := alertmanagertypes.AdditionalFuncMap()
|
||||
tree := parse.New("template")
|
||||
tree.Mode = parse.SkipFuncCheck
|
||||
if _, err := tree.Parse(preamble.String()+src, "{{", "}}", make(map[string]*parse.Tree), funcMap); err != nil {
|
||||
return nil, errors.WrapInvalidInputf(err, errors.CodeInternal, "failed to extract used variables")
|
||||
}
|
||||
|
||||
return used, nil
|
||||
}
|
||||
|
||||
// aggregateKV aggregates key-value pairs (labels or annotations) from all alerts into a single template.KV
|
||||
// the result is used to populate the labels and annotations in the notification template data.
|
||||
// this is done to avoid blank values in the template when labels and annotations used are not common throughout the alerts
|
||||
func aggregateKV(alerts []*types.Alert, extractFn func(*types.Alert) model.LabelSet) template.KV {
|
||||
// track unique values per key in order of first appearance
|
||||
valuesPerKey := make(map[string][]string)
|
||||
// track which values have been seen for deduplication
|
||||
seenValues := make(map[string]map[string]bool)
|
||||
|
||||
for _, alert := range alerts {
|
||||
kvPairs := extractFn(alert)
|
||||
for k, v := range kvPairs {
|
||||
key := string(k)
|
||||
value := string(v)
|
||||
|
||||
if seenValues[key] == nil {
|
||||
seenValues[key] = make(map[string]bool)
|
||||
}
|
||||
|
||||
// only add if not already seen and under the limit of maxAggregatedValues
|
||||
if !seenValues[key][value] && len(valuesPerKey[key]) < maxAggregatedValues {
|
||||
seenValues[key][value] = true
|
||||
valuesPerKey[key] = append(valuesPerKey[key], value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// build the result by joining values
|
||||
result := make(template.KV, len(valuesPerKey))
|
||||
for key, values := range valuesPerKey {
|
||||
result[key] = strings.Join(values, ", ")
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// extractCommonKV returns the intersection of key-value pairs across all alerts.
|
||||
// A key/value pair is included only if it appears identically on every alert.
|
||||
func extractCommonKV(alerts []*types.Alert, extractFn func(*types.Alert) model.LabelSet) template.KV {
|
||||
if len(alerts) == 0 {
|
||||
return template.KV{}
|
||||
}
|
||||
|
||||
common := make(template.KV, len(extractFn(alerts[0])))
|
||||
for k, v := range extractFn(alerts[0]) {
|
||||
common[string(k)] = string(v)
|
||||
}
|
||||
|
||||
for _, a := range alerts[1:] {
|
||||
kv := extractFn(a)
|
||||
for k := range common {
|
||||
if string(kv[model.LabelName(k)]) != common[k] {
|
||||
delete(common, k)
|
||||
}
|
||||
}
|
||||
if len(common) == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return common
|
||||
}
|
||||
@@ -1,348 +0,0 @@
|
||||
package alertmanagertemplate
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestWrapBareVars(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "mixed variables with actions",
|
||||
input: "$name is {{.Status}}",
|
||||
expected: "{{ .name }} is {{.Status}}",
|
||||
},
|
||||
{
|
||||
name: "nested variables in range",
|
||||
input: `{{range .items}}
|
||||
$title
|
||||
{{end}}`,
|
||||
expected: `{{range .items}}
|
||||
{{ .title }}
|
||||
{{end}}`,
|
||||
},
|
||||
{
|
||||
name: "nested variables in if else",
|
||||
input: "{{if .ok}}$a{{else}}$b{{end}}",
|
||||
expected: "{{if .ok}}{{ .a }}{{else}}{{ .b }}{{end}}",
|
||||
},
|
||||
// Labels prefix: index into .labels map
|
||||
{
|
||||
name: "labels variables prefix simple",
|
||||
input: "$labels.service",
|
||||
expected: `{{ index .labels "service" }}`,
|
||||
},
|
||||
{
|
||||
name: "labels variables prefix nested with multiple dots",
|
||||
input: "$labels.http.status",
|
||||
expected: `{{ index .labels "http.status" }}`,
|
||||
},
|
||||
{
|
||||
name: "multiple labels variables simple and nested",
|
||||
input: "$labels.service and $labels.instance.id",
|
||||
expected: `{{ index .labels "service" }} and {{ index .labels "instance.id" }}`,
|
||||
},
|
||||
// Annotations prefix: index into .annotations map
|
||||
{
|
||||
name: "annotations variables prefix simple",
|
||||
input: "$annotations.summary",
|
||||
expected: `{{ index .annotations "summary" }}`,
|
||||
},
|
||||
{
|
||||
name: "annotations variables prefix nested with multiple dots",
|
||||
input: "$annotations.alert.url",
|
||||
expected: `{{ index .annotations "alert.url" }}`,
|
||||
},
|
||||
// Other dotted paths: index into root context
|
||||
{
|
||||
name: "other variables with multiple dots",
|
||||
input: "$service.name",
|
||||
expected: `{{ index . "service.name" }}`,
|
||||
},
|
||||
{
|
||||
name: "other variables with multiple dots nested",
|
||||
input: "$http.status.code",
|
||||
expected: `{{ index . "http.status.code" }}`,
|
||||
},
|
||||
// Hybrid: all types combined
|
||||
{
|
||||
name: "hybrid - all variables types",
|
||||
input: "Alert: $alert_name Labels: $labels.severity Annotations: $annotations.desc Service: $service.name Count: $error_count",
|
||||
expected: `Alert: {{ .alert_name }} Labels: {{ index .labels "severity" }} Annotations: {{ index .annotations "desc" }} Service: {{ index . "service.name" }} Count: {{ .error_count }}`,
|
||||
},
|
||||
{
|
||||
name: "already wrapped should not be changed",
|
||||
input: "{{$status := .status}}{{.name}} is {{$status | toUpper}}",
|
||||
expected: "{{$status := .status}}{{.name}} is {{$status | toUpper}}",
|
||||
},
|
||||
{
|
||||
name: "no variables should not be changed",
|
||||
input: "Hello world",
|
||||
expected: "Hello world",
|
||||
},
|
||||
{
|
||||
name: "empty string",
|
||||
input: "",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "deeply nested",
|
||||
input: "{{range .items}}{{if .ok}}$deep{{end}}{{end}}",
|
||||
expected: "{{range .items}}{{if .ok}}{{ .deep }}{{end}}{{end}}",
|
||||
},
|
||||
{
|
||||
name: "complex example",
|
||||
input: `Hello $name, your score is $score.
|
||||
{{if .isAdmin}}
|
||||
Welcome back $name, you have {{.unreadCount}} messages.
|
||||
{{end}}`,
|
||||
expected: `Hello {{ .name }}, your score is {{ .score }}.
|
||||
{{if .isAdmin}}
|
||||
Welcome back {{ .name }}, you have {{.unreadCount}} messages.
|
||||
{{end}}`,
|
||||
},
|
||||
{
|
||||
name: "with custom function",
|
||||
input: "$name triggered at {{urlescape .url}}",
|
||||
expected: "{{ .name }} triggered at {{urlescape .url}}",
|
||||
},
|
||||
{
|
||||
name: "invalid template",
|
||||
input: "{{invalid",
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result, err := WrapDollarVariables(tc.input)
|
||||
|
||||
if tc.expectError {
|
||||
require.Error(t, err, "should error on invalid template syntax")
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractUsedVariables(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
input string
|
||||
expected map[string]bool
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "simple usage in text",
|
||||
input: "$name is $status",
|
||||
expected: map[string]bool{"name": true, "status": true},
|
||||
},
|
||||
{
|
||||
name: "declared in action block",
|
||||
input: "{{ $name := .name }}",
|
||||
expected: map[string]bool{"name": true},
|
||||
},
|
||||
{
|
||||
name: "range loop vars",
|
||||
input: "{{ range $i, $v := .items }}{{ end }}",
|
||||
expected: map[string]bool{"i": true, "v": true},
|
||||
},
|
||||
{
|
||||
name: "mixed text and action",
|
||||
input: "$x and {{ $y }}",
|
||||
expected: map[string]bool{"x": true, "y": true},
|
||||
},
|
||||
{
|
||||
name: "dotted path in text extracts base only",
|
||||
input: "$labels.severity",
|
||||
expected: map[string]bool{"labels": true},
|
||||
},
|
||||
{
|
||||
name: "nested if else",
|
||||
input: "{{ if .ok }}{{ $a }}{{ else }}{{ $b }}{{ end }}",
|
||||
expected: map[string]bool{"a": true, "b": true},
|
||||
},
|
||||
{
|
||||
name: "empty string",
|
||||
input: "",
|
||||
expected: map[string]bool{},
|
||||
},
|
||||
{
|
||||
name: "no variables",
|
||||
input: "Hello world",
|
||||
expected: map[string]bool{},
|
||||
},
|
||||
{
|
||||
name: "invalid template returns error",
|
||||
input: "{{invalid",
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result, err := ExtractUsedVariables(tc.input)
|
||||
|
||||
if tc.expectError {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregateKV(t *testing.T) {
|
||||
extractLabels := func(a *types.Alert) model.LabelSet { return a.Labels }
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
alerts []*types.Alert
|
||||
extractFn func(*types.Alert) model.LabelSet
|
||||
expected template.KV
|
||||
}{
|
||||
{
|
||||
name: "empty alerts slice",
|
||||
alerts: []*types.Alert{},
|
||||
extractFn: extractLabels,
|
||||
expected: template.KV{},
|
||||
},
|
||||
{
|
||||
name: "single alert",
|
||||
alerts: []*types.Alert{
|
||||
{
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{
|
||||
"env": "production",
|
||||
"service": "backend",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
extractFn: extractLabels,
|
||||
expected: template.KV{
|
||||
"env": "production",
|
||||
"service": "backend",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "varying values with duplicates deduped",
|
||||
alerts: []*types.Alert{
|
||||
{Alert: model.Alert{Labels: model.LabelSet{"env": "production", "service": "backend"}}},
|
||||
{Alert: model.Alert{Labels: model.LabelSet{"env": "production", "service": "api"}}},
|
||||
{Alert: model.Alert{Labels: model.LabelSet{"env": "production", "service": "frontend"}}},
|
||||
{Alert: model.Alert{Labels: model.LabelSet{"env": "production", "service": "api"}}},
|
||||
},
|
||||
extractFn: extractLabels,
|
||||
expected: template.KV{
|
||||
"env": "production",
|
||||
"service": "backend, api, frontend",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "more than 5 unique values truncates to 5",
|
||||
alerts: []*types.Alert{
|
||||
{Alert: model.Alert{Labels: model.LabelSet{"service": "svc1"}}},
|
||||
{Alert: model.Alert{Labels: model.LabelSet{"service": "svc2"}}},
|
||||
{Alert: model.Alert{Labels: model.LabelSet{"service": "svc3"}}},
|
||||
{Alert: model.Alert{Labels: model.LabelSet{"service": "svc4"}}},
|
||||
{Alert: model.Alert{Labels: model.LabelSet{"service": "svc5"}}},
|
||||
{Alert: model.Alert{Labels: model.LabelSet{"service": "svc6"}}},
|
||||
{Alert: model.Alert{Labels: model.LabelSet{"service": "svc7"}}},
|
||||
},
|
||||
extractFn: extractLabels,
|
||||
expected: template.KV{
|
||||
"service": "svc1, svc2, svc3, svc4, svc5",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := aggregateKV(tc.alerts, tc.extractFn)
|
||||
require.Equal(t, tc.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractCommonKV(t *testing.T) {
|
||||
extractLabels := func(a *types.Alert) model.LabelSet { return a.Labels }
|
||||
extractAnnotations := func(a *types.Alert) model.LabelSet { return a.Annotations }
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
alerts []*types.Alert
|
||||
extractFn func(*types.Alert) model.LabelSet
|
||||
expected template.KV
|
||||
}{
|
||||
{
|
||||
name: "empty alerts slice",
|
||||
alerts: []*types.Alert{},
|
||||
extractFn: extractLabels,
|
||||
expected: template.KV{},
|
||||
},
|
||||
{
|
||||
name: "single alert returns all labels",
|
||||
alerts: []*types.Alert{
|
||||
{Alert: model.Alert{Labels: model.LabelSet{"env": "prod", "service": "api"}}},
|
||||
},
|
||||
extractFn: extractLabels,
|
||||
expected: template.KV{"env": "prod", "service": "api"},
|
||||
},
|
||||
{
|
||||
name: "multiple alerts with fully common labels",
|
||||
alerts: []*types.Alert{
|
||||
{Alert: model.Alert{Labels: model.LabelSet{"env": "prod", "region": "us-east"}}},
|
||||
{Alert: model.Alert{Labels: model.LabelSet{"env": "prod", "region": "us-east"}}},
|
||||
},
|
||||
extractFn: extractLabels,
|
||||
expected: template.KV{"env": "prod", "region": "us-east"},
|
||||
},
|
||||
{
|
||||
name: "multiple alerts with partially common labels",
|
||||
alerts: []*types.Alert{
|
||||
{Alert: model.Alert{Labels: model.LabelSet{"env": "prod", "service": "api"}}},
|
||||
{Alert: model.Alert{Labels: model.LabelSet{"env": "prod", "service": "worker"}}},
|
||||
},
|
||||
extractFn: extractLabels,
|
||||
expected: template.KV{"env": "prod"},
|
||||
},
|
||||
{
|
||||
name: "multiple alerts with no common labels",
|
||||
alerts: []*types.Alert{
|
||||
{Alert: model.Alert{Labels: model.LabelSet{"service": "api"}}},
|
||||
{Alert: model.Alert{Labels: model.LabelSet{"service": "worker"}}},
|
||||
},
|
||||
extractFn: extractLabels,
|
||||
expected: template.KV{},
|
||||
},
|
||||
{
|
||||
name: "annotations extract common annotations",
|
||||
alerts: []*types.Alert{
|
||||
{Alert: model.Alert{Annotations: model.LabelSet{"summary": "high cpu", "runbook": "http://x"}}},
|
||||
{Alert: model.Alert{Annotations: model.LabelSet{"summary": "high cpu", "runbook": "http://y"}}},
|
||||
},
|
||||
extractFn: extractAnnotations,
|
||||
expected: template.KV{"summary": "high cpu"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := extractCommonKV(tc.alerts, tc.extractFn)
|
||||
require.Equal(t, tc.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
@@ -22,7 +21,7 @@ func (provider *provider) addAuthDomainRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -39,7 +38,7 @@ func (provider *provider) addAuthDomainRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -56,7 +55,7 @@ func (provider *provider) addAuthDomainRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -73,7 +72,7 @@ func (provider *provider) addAuthDomainRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -42,7 +42,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -59,7 +59,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -76,7 +76,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"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/telemetrytypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
@@ -23,7 +23,7 @@ func (provider *provider) addFieldsRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -41,7 +41,7 @@ func (provider *provider) addFieldsRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"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/featuretypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
@@ -22,7 +22,7 @@ func (provider *provider) addFlaggerRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"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/gatewaytypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
@@ -23,7 +23,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -41,7 +41,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -58,7 +58,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -75,7 +75,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodPatch).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -92,7 +92,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -109,7 +109,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -126,7 +126,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodPatch).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -143,7 +143,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
@@ -21,7 +22,7 @@ func (provider *provider) addGlobalRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleEditor),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"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/metricsexplorertypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
@@ -25,7 +25,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusInternalServerError},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -44,7 +44,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusInternalServerError},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -63,7 +63,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusInternalServerError},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -83,7 +83,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusNotFound, http.StatusInternalServerError},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -102,7 +102,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusNotFound, http.StatusInternalServerError},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -121,7 +121,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusInternalServerError},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleEditor),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -140,7 +140,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusNotFound, http.StatusInternalServerError},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -159,7 +159,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusNotFound, http.StatusInternalServerError},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -178,7 +178,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusNotFound, http.StatusInternalServerError},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
@@ -21,7 +22,7 @@ func (provider *provider) addOrgRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -38,7 +39,7 @@ func (provider *provider) addOrgRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusConflict, http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"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/preferencetypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
@@ -22,7 +22,7 @@ func (provider *provider) addPreferenceRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -39,7 +39,7 @@ func (provider *provider) addPreferenceRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -56,7 +56,7 @@ func (provider *provider) addPreferenceRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -73,7 +73,7 @@ func (provider *provider) addPreferenceRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -90,7 +90,7 @@ func (provider *provider) addPreferenceRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -107,7 +107,7 @@ func (provider *provider) addPreferenceRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"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/promotetypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
@@ -21,7 +21,7 @@ func (provider *provider) addPromoteRoutes(router *mux.Router) error {
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleEditor),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -37,7 +37,7 @@ func (provider *provider) addPromoteRoutes(router *mux.Router) error {
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/session"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
"github.com/SigNoz/signoz/pkg/querier"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/zeus"
|
||||
"github.com/gorilla/mux"
|
||||
@@ -236,7 +235,7 @@ func (provider *provider) AddToRouter(router *mux.Router) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func newSecuritySchemes(role types.Role) []handler.OpenAPISecurityScheme {
|
||||
func newSecuritySchemes(role authtypes.LegacyRole) []handler.OpenAPISecurityScheme {
|
||||
return []handler.OpenAPISecurityScheme{
|
||||
{Name: authtypes.IdentNProviderAPIkey.StringValue(), Scopes: []string{role.String()}},
|
||||
{Name: authtypes.IdentNProviderTokenizer.StringValue(), Scopes: []string{role.String()}},
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
@@ -446,7 +446,7 @@ func (provider *provider) addQuerierRoutes(router *mux.Router) error {
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -462,7 +462,7 @@ func (provider *provider) addQuerierRoutes(router *mux.Router) error {
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ 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/roletypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
@@ -16,14 +15,14 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
|
||||
Tags: []string{"role"},
|
||||
Summary: "Create role",
|
||||
Description: "This endpoint creates a role",
|
||||
Request: new(roletypes.PostableRole),
|
||||
Request: new(authtypes.PostableRole),
|
||||
RequestContentType: "",
|
||||
Response: new(types.Identifiable),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict, http.StatusNotImplemented, http.StatusUnavailableForLegalReasons},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -35,12 +34,12 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
|
||||
Description: "This endpoint lists all roles",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: make([]*roletypes.Role, 0),
|
||||
Response: make([]*authtypes.Role, 0),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -52,12 +51,12 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
|
||||
Description: "This endpoint gets a role",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: new(roletypes.Role),
|
||||
Response: new(authtypes.Role),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -74,7 +73,7 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusNotImplemented, http.StatusUnavailableForLegalReasons},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -84,14 +83,14 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
|
||||
Tags: []string{"role"},
|
||||
Summary: "Patch role",
|
||||
Description: "This endpoint patches a role",
|
||||
Request: new(roletypes.PatchableRole),
|
||||
Request: new(authtypes.PatchableRole),
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusNotImplemented, http.StatusUnavailableForLegalReasons},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodPatch).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -108,7 +107,7 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusBadRequest, http.StatusNotImplemented, http.StatusUnavailableForLegalReasons},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodPatch).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -125,7 +124,7 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusNotImplemented, http.StatusUnavailableForLegalReasons},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ 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/serviceaccounttypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
@@ -22,7 +23,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -39,7 +40,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -56,7 +57,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -73,7 +74,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -90,7 +91,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -107,7 +108,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -124,7 +125,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -141,7 +142,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -158,7 +159,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -175,7 +176,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"net/http"
|
||||
|
||||
"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/usertypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
@@ -15,14 +15,14 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
Tags: []string{"users"},
|
||||
Summary: "Create invite",
|
||||
Description: "This endpoint creates an invite for a user",
|
||||
Request: new(types.PostableInvite),
|
||||
Request: new(usertypes.PostableInvite),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(types.Invite),
|
||||
Response: new(usertypes.Invite),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -32,13 +32,13 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
Tags: []string{"users"},
|
||||
Summary: "Create bulk invite",
|
||||
Description: "This endpoint creates a bulk invite for a user",
|
||||
Request: new(types.PostableBulkInviteRequest),
|
||||
Request: new(usertypes.PostableBulkInviteRequest),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -50,7 +50,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
Description: "This endpoint gets an invite by token",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: new(types.Invite),
|
||||
Response: new(usertypes.Invite),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
@@ -72,7 +72,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -84,12 +84,12 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
Description: "This endpoint lists all invites",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: make([]*types.Invite, 0),
|
||||
Response: make([]*usertypes.Invite, 0),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -99,9 +99,9 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
Tags: []string{"users"},
|
||||
Summary: "Accept invite",
|
||||
Description: "This endpoint accepts an invite by token",
|
||||
Request: new(types.PostableAcceptInvite),
|
||||
Request: new(usertypes.PostableAcceptInvite),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(types.User),
|
||||
Response: new(usertypes.User),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
@@ -116,14 +116,14 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
Tags: []string{"users"},
|
||||
Summary: "Create api key",
|
||||
Description: "This endpoint creates an api key",
|
||||
Request: new(types.PostableAPIKey),
|
||||
Request: new(usertypes.PostableAPIKey),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(types.GettableAPIKey),
|
||||
Response: new(usertypes.GettableAPIKey),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -135,12 +135,12 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
Description: "This endpoint lists all api keys",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: make([]*types.GettableAPIKey, 0),
|
||||
Response: make([]*usertypes.GettableAPIKey, 0),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -150,14 +150,14 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
Tags: []string{"users"},
|
||||
Summary: "Update api key",
|
||||
Description: "This endpoint updates an api key",
|
||||
Request: new(types.StorableAPIKey),
|
||||
Request: new(usertypes.StorableAPIKey),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -174,7 +174,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -186,12 +186,12 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
Description: "This endpoint lists all users",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: make([]*types.GettableUser, 0),
|
||||
Response: make([]*usertypes.GettableUser, 0),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -203,7 +203,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
Description: "This endpoint returns the user I belong to",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: new(types.GettableUser),
|
||||
Response: new(usertypes.GettableUser),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
@@ -220,12 +220,12 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
Description: "This endpoint returns the user by id",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: new(types.GettableUser),
|
||||
Response: new(usertypes.GettableUser),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -235,14 +235,14 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
Tags: []string{"users"},
|
||||
Summary: "Update user",
|
||||
Description: "This endpoint updates the user by id",
|
||||
Request: new(types.User),
|
||||
Request: new(usertypes.User),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(types.GettableUser),
|
||||
Response: new(usertypes.GettableUser),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -259,7 +259,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -271,12 +271,12 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
Description: "This endpoint returns the reset password token by id",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: new(types.ResetPasswordToken),
|
||||
Response: new(usertypes.ResetPasswordToken),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -286,7 +286,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
Tags: []string{"users"},
|
||||
Summary: "Reset password",
|
||||
Description: "This endpoint resets the password by token",
|
||||
Request: new(types.PostableResetPassword),
|
||||
Request: new(usertypes.PostableResetPassword),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
@@ -303,14 +303,14 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
Tags: []string{"users"},
|
||||
Summary: "Change password",
|
||||
Description: "This endpoint changes the password by id",
|
||||
Request: new(types.ChangePasswordRequest),
|
||||
Request: new(usertypes.ChangePasswordRequest),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -320,7 +320,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
Tags: []string{"users"},
|
||||
Summary: "Forgot password",
|
||||
Description: "This endpoint initiates the forgot password flow by sending a reset password email",
|
||||
Request: new(types.PostableForgotPassword),
|
||||
Request: new(usertypes.PostableForgotPassword),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"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/zeustypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
@@ -22,7 +22,7 @@ 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),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -39,7 +39,7 @@ 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.RoleAdmin),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -56,7 +56,7 @@ 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),
|
||||
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
@@ -17,37 +16,6 @@ func NewStore(sqlstore sqlstore.SQLStore) authtypes.AuthNStore {
|
||||
return &store{sqlstore: sqlstore}
|
||||
}
|
||||
|
||||
func (store *store) GetActiveUserAndFactorPasswordByEmailAndOrgID(ctx context.Context, email string, orgID valuer.UUID) (*types.User, *types.FactorPassword, error) {
|
||||
user := new(types.User)
|
||||
factorPassword := new(types.FactorPassword)
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewSelect().
|
||||
Model(user).
|
||||
Where("email = ?", email).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("status = ?", types.UserStatusActive.StringValue()).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeUserNotFound, "user with email %s in org %s not found", email, orgID)
|
||||
}
|
||||
|
||||
err = store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewSelect().
|
||||
Model(factorPassword).
|
||||
Where("user_id = ?", user.ID).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodePasswordNotFound, "user with email %s in org %s does not have password", email, orgID)
|
||||
}
|
||||
|
||||
return user, factorPassword, nil
|
||||
}
|
||||
|
||||
func (store *store) GetAuthDomainFromID(ctx context.Context, domainID valuer.UUID) (*authtypes.AuthDomain, error) {
|
||||
storableAuthDomain := new(authtypes.StorableAuthDomain)
|
||||
|
||||
|
||||
@@ -5,29 +5,30 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/authn"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/usertypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
var _ authn.PasswordAuthN = (*AuthN)(nil)
|
||||
|
||||
type AuthN struct {
|
||||
store authtypes.AuthNStore
|
||||
userGetter user.Getter
|
||||
}
|
||||
|
||||
func New(store authtypes.AuthNStore) *AuthN {
|
||||
return &AuthN{store: store}
|
||||
func New(userGetter user.Getter) *AuthN {
|
||||
return &AuthN{userGetter: userGetter}
|
||||
}
|
||||
|
||||
func (a *AuthN) Authenticate(ctx context.Context, email string, password string, orgID valuer.UUID) (*authtypes.Identity, error) {
|
||||
user, factorPassword, err := a.store.GetActiveUserAndFactorPasswordByEmailAndOrgID(ctx, email, orgID)
|
||||
user, factorPassword, err := a.userGetter.GetActiveUserAndFactorPasswordByEmailAndOrgID(ctx, email, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !factorPassword.Equals(password) {
|
||||
return nil, errors.New(errors.TypeUnauthenticated, types.ErrCodeIncorrectPassword, "invalid email or password")
|
||||
return nil, errors.New(errors.TypeUnauthenticated, usertypes.ErrCodeIncorrectPassword, "invalid email or password")
|
||||
}
|
||||
|
||||
return authtypes.NewIdentity(user.ID, orgID, user.Email, user.Role, authtypes.IdentNProviderTokenizer), nil
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
)
|
||||
@@ -30,10 +29,10 @@ type AuthZ interface {
|
||||
ListObjects(context.Context, string, authtypes.Relation, authtypes.Typeable) ([]*authtypes.Object, error)
|
||||
|
||||
// Creates the role.
|
||||
Create(context.Context, valuer.UUID, *roletypes.Role) error
|
||||
Create(context.Context, valuer.UUID, *authtypes.Role) error
|
||||
|
||||
// Gets the role if it exists or creates one.
|
||||
GetOrCreate(context.Context, valuer.UUID, *roletypes.Role) (*roletypes.Role, error)
|
||||
GetOrCreate(context.Context, valuer.UUID, *authtypes.Role) (*authtypes.Role, error)
|
||||
|
||||
// Gets the objects associated with the given role and relation.
|
||||
GetObjects(context.Context, valuer.UUID, valuer.UUID, authtypes.Relation) ([]*authtypes.Object, error)
|
||||
@@ -42,7 +41,7 @@ type AuthZ interface {
|
||||
GetResources(context.Context) []*authtypes.Resource
|
||||
|
||||
// Patches the role.
|
||||
Patch(context.Context, valuer.UUID, *roletypes.Role) error
|
||||
Patch(context.Context, valuer.UUID, *authtypes.Role) error
|
||||
|
||||
// Patches the objects in authorization server associated with the given role and relation
|
||||
PatchObjects(context.Context, valuer.UUID, string, authtypes.Relation, []*authtypes.Object, []*authtypes.Object) error
|
||||
@@ -51,19 +50,19 @@ type AuthZ interface {
|
||||
Delete(context.Context, valuer.UUID, valuer.UUID) error
|
||||
|
||||
// Gets the role
|
||||
Get(context.Context, valuer.UUID, valuer.UUID) (*roletypes.Role, error)
|
||||
Get(context.Context, valuer.UUID, valuer.UUID) (*authtypes.Role, error)
|
||||
|
||||
// Gets the role by org_id and name
|
||||
GetByOrgIDAndName(context.Context, valuer.UUID, string) (*roletypes.Role, error)
|
||||
GetByOrgIDAndName(context.Context, valuer.UUID, string) (*authtypes.Role, error)
|
||||
|
||||
// Lists all the roles for the organization.
|
||||
List(context.Context, valuer.UUID) ([]*roletypes.Role, error)
|
||||
List(context.Context, valuer.UUID) ([]*authtypes.Role, error)
|
||||
|
||||
// Lists all the roles for the organization filtered by name
|
||||
ListByOrgIDAndNames(context.Context, valuer.UUID, []string) ([]*roletypes.Role, error)
|
||||
ListByOrgIDAndNames(context.Context, valuer.UUID, []string) ([]*authtypes.Role, error)
|
||||
|
||||
// Lists all the roles for the organization filtered by ids
|
||||
ListByOrgIDAndIDs(context.Context, valuer.UUID, []valuer.UUID) ([]*roletypes.Role, error)
|
||||
ListByOrgIDAndIDs(context.Context, valuer.UUID, []valuer.UUID) ([]*authtypes.Role, error)
|
||||
|
||||
// Grants a role to the subject based on role name.
|
||||
Grant(context.Context, valuer.UUID, []string, string) error
|
||||
@@ -75,7 +74,7 @@ type AuthZ interface {
|
||||
ModifyGrant(context.Context, valuer.UUID, []string, []string, string) error
|
||||
|
||||
// Bootstrap the managed roles.
|
||||
CreateManagedRoles(context.Context, valuer.UUID, []*roletypes.Role) error
|
||||
CreateManagedRoles(context.Context, valuer.UUID, []*authtypes.Role) error
|
||||
|
||||
// Bootstrap managed roles transactions and user assignments
|
||||
CreateManagedUserRoleTransactions(context.Context, valuer.UUID, valuer.UUID) error
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
@@ -14,11 +14,11 @@ type store struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
}
|
||||
|
||||
func NewSqlAuthzStore(sqlstore sqlstore.SQLStore) roletypes.Store {
|
||||
func NewSqlAuthzStore(sqlstore sqlstore.SQLStore) authtypes.RoleStore {
|
||||
return &store{sqlstore: sqlstore}
|
||||
}
|
||||
|
||||
func (store *store) Create(ctx context.Context, role *roletypes.StorableRole) error {
|
||||
func (store *store) Create(ctx context.Context, role *authtypes.StorableRole) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
@@ -32,8 +32,8 @@ func (store *store) Create(ctx context.Context, role *roletypes.StorableRole) er
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*roletypes.StorableRole, error) {
|
||||
role := new(roletypes.StorableRole)
|
||||
func (store *store) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*authtypes.StorableRole, error) {
|
||||
role := new(authtypes.StorableRole)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
@@ -43,14 +43,14 @@ func (store *store) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID)
|
||||
Where("id = ?", id).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, roletypes.ErrCodeRoleNotFound, "role with id: %s doesn't exist", id)
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, authtypes.ErrCodeRoleNotFound, "role with id: %s doesn't exist", id)
|
||||
}
|
||||
|
||||
return role, nil
|
||||
}
|
||||
|
||||
func (store *store) GetByOrgIDAndName(ctx context.Context, orgID valuer.UUID, name string) (*roletypes.StorableRole, error) {
|
||||
role := new(roletypes.StorableRole)
|
||||
func (store *store) GetByOrgIDAndName(ctx context.Context, orgID valuer.UUID, name string) (*authtypes.StorableRole, error) {
|
||||
role := new(authtypes.StorableRole)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
@@ -60,14 +60,14 @@ func (store *store) GetByOrgIDAndName(ctx context.Context, orgID valuer.UUID, na
|
||||
Where("name = ?", name).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, roletypes.ErrCodeRoleNotFound, "role with name: %s doesn't exist", name)
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, authtypes.ErrCodeRoleNotFound, "role with name: %s doesn't exist", name)
|
||||
}
|
||||
|
||||
return role, nil
|
||||
}
|
||||
|
||||
func (store *store) List(ctx context.Context, orgID valuer.UUID) ([]*roletypes.StorableRole, error) {
|
||||
roles := make([]*roletypes.StorableRole, 0)
|
||||
func (store *store) List(ctx context.Context, orgID valuer.UUID) ([]*authtypes.StorableRole, error) {
|
||||
roles := make([]*authtypes.StorableRole, 0)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
@@ -82,8 +82,8 @@ func (store *store) List(ctx context.Context, orgID valuer.UUID) ([]*roletypes.S
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func (store *store) ListByOrgIDAndNames(ctx context.Context, orgID valuer.UUID, names []string) ([]*roletypes.StorableRole, error) {
|
||||
roles := make([]*roletypes.StorableRole, 0)
|
||||
func (store *store) ListByOrgIDAndNames(ctx context.Context, orgID valuer.UUID, names []string) ([]*authtypes.StorableRole, error) {
|
||||
roles := make([]*authtypes.StorableRole, 0)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
@@ -99,7 +99,7 @@ func (store *store) ListByOrgIDAndNames(ctx context.Context, orgID valuer.UUID,
|
||||
if len(roles) != len(names) {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(
|
||||
nil,
|
||||
roletypes.ErrCodeRoleNotFound,
|
||||
authtypes.ErrCodeRoleNotFound,
|
||||
"not all roles found for the provided names: %v", names,
|
||||
)
|
||||
}
|
||||
@@ -107,8 +107,8 @@ func (store *store) ListByOrgIDAndNames(ctx context.Context, orgID valuer.UUID,
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func (store *store) ListByOrgIDAndIDs(ctx context.Context, orgID valuer.UUID, ids []valuer.UUID) ([]*roletypes.StorableRole, error) {
|
||||
roles := make([]*roletypes.StorableRole, 0)
|
||||
func (store *store) ListByOrgIDAndIDs(ctx context.Context, orgID valuer.UUID, ids []valuer.UUID) ([]*authtypes.StorableRole, error) {
|
||||
roles := make([]*authtypes.StorableRole, 0)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
@@ -124,7 +124,7 @@ func (store *store) ListByOrgIDAndIDs(ctx context.Context, orgID valuer.UUID, id
|
||||
if len(roles) != len(ids) {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(
|
||||
nil,
|
||||
roletypes.ErrCodeRoleNotFound,
|
||||
authtypes.ErrCodeRoleNotFound,
|
||||
"not all roles found for the provided ids: %v", ids,
|
||||
)
|
||||
}
|
||||
@@ -132,7 +132,7 @@ func (store *store) ListByOrgIDAndIDs(ctx context.Context, orgID valuer.UUID, id
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func (store *store) Update(ctx context.Context, orgID valuer.UUID, role *roletypes.StorableRole) error {
|
||||
func (store *store) Update(ctx context.Context, orgID valuer.UUID, role *authtypes.StorableRole) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
@@ -153,12 +153,12 @@ func (store *store) Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUI
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewDelete().
|
||||
Model(new(roletypes.StorableRole)).
|
||||
Model(new(authtypes.StorableRole)).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("id = ?", id).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return store.sqlstore.WrapNotFoundErrf(err, roletypes.ErrCodeRoleNotFound, "role with id %s doesn't exist", id)
|
||||
return store.sqlstore.WrapNotFoundErrf(err, authtypes.ErrCodeRoleNotFound, "role with id %s doesn't exist", id)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/authz/openfgaserver"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
@@ -19,7 +18,7 @@ import (
|
||||
|
||||
type provider struct {
|
||||
server *openfgaserver.Server
|
||||
store roletypes.Store
|
||||
store authtypes.RoleStore
|
||||
}
|
||||
|
||||
func NewProviderFactory(sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile) factory.ProviderFactory[authz.AuthZ, authz.Config] {
|
||||
@@ -68,61 +67,61 @@ func (provider *provider) ListObjects(ctx context.Context, subject string, relat
|
||||
return provider.server.ListObjects(ctx, subject, relation, typeable)
|
||||
}
|
||||
|
||||
func (provider *provider) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*roletypes.Role, error) {
|
||||
func (provider *provider) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*authtypes.Role, error) {
|
||||
storableRole, err := provider.store.Get(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return roletypes.NewRoleFromStorableRole(storableRole), nil
|
||||
return authtypes.NewRoleFromStorableRole(storableRole), nil
|
||||
}
|
||||
|
||||
func (provider *provider) GetByOrgIDAndName(ctx context.Context, orgID valuer.UUID, name string) (*roletypes.Role, error) {
|
||||
func (provider *provider) GetByOrgIDAndName(ctx context.Context, orgID valuer.UUID, name string) (*authtypes.Role, error) {
|
||||
storableRole, err := provider.store.GetByOrgIDAndName(ctx, orgID, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return roletypes.NewRoleFromStorableRole(storableRole), nil
|
||||
return authtypes.NewRoleFromStorableRole(storableRole), nil
|
||||
}
|
||||
|
||||
func (provider *provider) List(ctx context.Context, orgID valuer.UUID) ([]*roletypes.Role, error) {
|
||||
func (provider *provider) List(ctx context.Context, orgID valuer.UUID) ([]*authtypes.Role, error) {
|
||||
storableRoles, err := provider.store.List(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
roles := make([]*roletypes.Role, len(storableRoles))
|
||||
roles := make([]*authtypes.Role, len(storableRoles))
|
||||
for idx, storableRole := range storableRoles {
|
||||
roles[idx] = roletypes.NewRoleFromStorableRole(storableRole)
|
||||
roles[idx] = authtypes.NewRoleFromStorableRole(storableRole)
|
||||
}
|
||||
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func (provider *provider) ListByOrgIDAndNames(ctx context.Context, orgID valuer.UUID, names []string) ([]*roletypes.Role, error) {
|
||||
func (provider *provider) ListByOrgIDAndNames(ctx context.Context, orgID valuer.UUID, names []string) ([]*authtypes.Role, error) {
|
||||
storableRoles, err := provider.store.ListByOrgIDAndNames(ctx, orgID, names)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
roles := make([]*roletypes.Role, len(storableRoles))
|
||||
roles := make([]*authtypes.Role, len(storableRoles))
|
||||
for idx, storable := range storableRoles {
|
||||
roles[idx] = roletypes.NewRoleFromStorableRole(storable)
|
||||
roles[idx] = authtypes.NewRoleFromStorableRole(storable)
|
||||
}
|
||||
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func (provider *provider) ListByOrgIDAndIDs(ctx context.Context, orgID valuer.UUID, ids []valuer.UUID) ([]*roletypes.Role, error) {
|
||||
func (provider *provider) ListByOrgIDAndIDs(ctx context.Context, orgID valuer.UUID, ids []valuer.UUID) ([]*authtypes.Role, error) {
|
||||
storableRoles, err := provider.store.ListByOrgIDAndIDs(ctx, orgID, ids)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
roles := make([]*roletypes.Role, len(storableRoles))
|
||||
roles := make([]*authtypes.Role, len(storableRoles))
|
||||
for idx, storable := range storableRoles {
|
||||
roles[idx] = roletypes.NewRoleFromStorableRole(storable)
|
||||
roles[idx] = authtypes.NewRoleFromStorableRole(storable)
|
||||
}
|
||||
|
||||
return roles, nil
|
||||
@@ -179,10 +178,10 @@ func (provider *provider) Revoke(ctx context.Context, orgID valuer.UUID, names [
|
||||
return provider.Write(ctx, nil, tuples)
|
||||
}
|
||||
|
||||
func (provider *provider) CreateManagedRoles(ctx context.Context, _ valuer.UUID, managedRoles []*roletypes.Role) error {
|
||||
func (provider *provider) CreateManagedRoles(ctx context.Context, _ valuer.UUID, managedRoles []*authtypes.Role) error {
|
||||
err := provider.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
for _, role := range managedRoles {
|
||||
err := provider.store.Create(ctx, roletypes.NewStorableRoleFromRole(role))
|
||||
err := provider.store.Create(ctx, authtypes.NewStorableRoleFromRole(role))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -199,15 +198,15 @@ func (provider *provider) CreateManagedRoles(ctx context.Context, _ valuer.UUID,
|
||||
}
|
||||
|
||||
func (provider *provider) CreateManagedUserRoleTransactions(ctx context.Context, orgID valuer.UUID, userID valuer.UUID) error {
|
||||
return provider.Grant(ctx, orgID, []string{roletypes.SigNozAdminRoleName}, authtypes.MustNewSubject(authtypes.TypeableUser, userID.String(), orgID, nil))
|
||||
return provider.Grant(ctx, orgID, []string{authtypes.SigNozAdminRoleName}, authtypes.MustNewSubject(authtypes.TypeableUser, userID.String(), orgID, nil))
|
||||
}
|
||||
|
||||
func (setter *provider) Create(_ context.Context, _ valuer.UUID, _ *roletypes.Role) error {
|
||||
return errors.Newf(errors.TypeUnsupported, roletypes.ErrCodeRoleUnsupported, "not implemented")
|
||||
func (setter *provider) Create(_ context.Context, _ valuer.UUID, _ *authtypes.Role) error {
|
||||
return errors.Newf(errors.TypeUnsupported, authtypes.ErrCodeRoleUnsupported, "not implemented")
|
||||
}
|
||||
|
||||
func (provider *provider) GetOrCreate(_ context.Context, _ valuer.UUID, _ *roletypes.Role) (*roletypes.Role, error) {
|
||||
return nil, errors.Newf(errors.TypeUnsupported, roletypes.ErrCodeRoleUnsupported, "not implemented")
|
||||
func (provider *provider) GetOrCreate(_ context.Context, _ valuer.UUID, _ *authtypes.Role) (*authtypes.Role, error) {
|
||||
return nil, errors.Newf(errors.TypeUnsupported, authtypes.ErrCodeRoleUnsupported, "not implemented")
|
||||
}
|
||||
|
||||
func (provider *provider) GetResources(_ context.Context) []*authtypes.Resource {
|
||||
@@ -215,19 +214,19 @@ func (provider *provider) GetResources(_ context.Context) []*authtypes.Resource
|
||||
}
|
||||
|
||||
func (provider *provider) GetObjects(ctx context.Context, orgID valuer.UUID, id valuer.UUID, relation authtypes.Relation) ([]*authtypes.Object, error) {
|
||||
return nil, errors.Newf(errors.TypeUnsupported, roletypes.ErrCodeRoleUnsupported, "not implemented")
|
||||
return nil, errors.Newf(errors.TypeUnsupported, authtypes.ErrCodeRoleUnsupported, "not implemented")
|
||||
}
|
||||
|
||||
func (provider *provider) Patch(_ context.Context, _ valuer.UUID, _ *roletypes.Role) error {
|
||||
return errors.Newf(errors.TypeUnsupported, roletypes.ErrCodeRoleUnsupported, "not implemented")
|
||||
func (provider *provider) Patch(_ context.Context, _ valuer.UUID, _ *authtypes.Role) error {
|
||||
return errors.Newf(errors.TypeUnsupported, authtypes.ErrCodeRoleUnsupported, "not implemented")
|
||||
}
|
||||
|
||||
func (provider *provider) PatchObjects(_ context.Context, _ valuer.UUID, _ string, _ authtypes.Relation, _, _ []*authtypes.Object) error {
|
||||
return errors.Newf(errors.TypeUnsupported, roletypes.ErrCodeRoleUnsupported, "not implemented")
|
||||
return errors.Newf(errors.TypeUnsupported, authtypes.ErrCodeRoleUnsupported, "not implemented")
|
||||
}
|
||||
|
||||
func (provider *provider) Delete(_ context.Context, _ valuer.UUID, _ valuer.UUID) error {
|
||||
return errors.Newf(errors.TypeUnsupported, roletypes.ErrCodeRoleUnsupported, "not implemented")
|
||||
return errors.Newf(errors.TypeUnsupported, authtypes.ErrCodeRoleUnsupported, "not implemented")
|
||||
}
|
||||
|
||||
func (provider *provider) MustGetTypeables() []authtypes.Typeable {
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
@@ -30,13 +29,13 @@ func (handler *handler) Create(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
req := new(roletypes.PostableRole)
|
||||
req := new(authtypes.PostableRole)
|
||||
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
role := roletypes.NewRole(req.Name, req.Description, roletypes.RoleTypeCustom, valuer.MustNewUUID(claims.OrgID))
|
||||
role := authtypes.NewRole(req.Name, req.Description, authtypes.RoleTypeCustom, valuer.MustNewUUID(claims.OrgID))
|
||||
err = handler.authz.Create(ctx, valuer.MustNewUUID(claims.OrgID), role)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
@@ -56,7 +55,7 @@ func (handler *handler) Get(rw http.ResponseWriter, r *http.Request) {
|
||||
|
||||
id, ok := mux.Vars(r)["id"]
|
||||
if !ok {
|
||||
render.Error(rw, errors.New(errors.TypeInvalidInput, roletypes.ErrCodeRoleInvalidInput, "id is missing from the request"))
|
||||
render.Error(rw, errors.New(errors.TypeInvalidInput, authtypes.ErrCodeRoleInvalidInput, "id is missing from the request"))
|
||||
return
|
||||
}
|
||||
roleID, err := valuer.NewUUID(id)
|
||||
@@ -84,7 +83,7 @@ func (handler *handler) GetObjects(rw http.ResponseWriter, r *http.Request) {
|
||||
|
||||
id, ok := mux.Vars(r)["id"]
|
||||
if !ok {
|
||||
render.Error(rw, errors.New(errors.TypeInvalidInput, roletypes.ErrCodeRoleInvalidInput, "id is missing from the request"))
|
||||
render.Error(rw, errors.New(errors.TypeInvalidInput, authtypes.ErrCodeRoleInvalidInput, "id is missing from the request"))
|
||||
return
|
||||
}
|
||||
roleID, err := valuer.NewUUID(id)
|
||||
@@ -95,7 +94,7 @@ func (handler *handler) GetObjects(rw http.ResponseWriter, r *http.Request) {
|
||||
|
||||
relationStr, ok := mux.Vars(r)["relation"]
|
||||
if !ok {
|
||||
render.Error(rw, errors.New(errors.TypeInvalidInput, roletypes.ErrCodeRoleInvalidInput, "relation is missing from the request"))
|
||||
render.Error(rw, errors.New(errors.TypeInvalidInput, authtypes.ErrCodeRoleInvalidInput, "relation is missing from the request"))
|
||||
return
|
||||
}
|
||||
relation, err := authtypes.NewRelation(relationStr)
|
||||
@@ -150,7 +149,7 @@ func (handler *handler) Patch(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
req := new(roletypes.PatchableRole)
|
||||
req := new(authtypes.PatchableRole)
|
||||
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
|
||||
@@ -147,7 +147,7 @@ func Ast(cause error, typ typ) bool {
|
||||
return t == typ
|
||||
}
|
||||
|
||||
// Asc checks if the provided error matches the specified custom error code.
|
||||
// Ast checks if the provided error matches the specified custom error code.
|
||||
func Asc(cause error, code Code) bool {
|
||||
_, c, _, _, _, _ := Unwrapb(cause)
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
@@ -56,9 +55,9 @@ func (middleware *AuthZ) ViewAccess(next http.HandlerFunc) http.HandlerFunc {
|
||||
}
|
||||
|
||||
selectors := []authtypes.Selector{
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, roletypes.SigNozAdminRoleName),
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, roletypes.SigNozEditorRoleName),
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, roletypes.SigNozViewerRoleName),
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, authtypes.SigNozAdminRoleName),
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, authtypes.SigNozEditorRoleName),
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, authtypes.SigNozViewerRoleName),
|
||||
}
|
||||
|
||||
err = middleware.authzService.CheckWithTupleCreation(
|
||||
@@ -108,8 +107,8 @@ func (middleware *AuthZ) EditAccess(next http.HandlerFunc) http.HandlerFunc {
|
||||
}
|
||||
|
||||
selectors := []authtypes.Selector{
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, roletypes.SigNozAdminRoleName),
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, roletypes.SigNozEditorRoleName),
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, authtypes.SigNozAdminRoleName),
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, authtypes.SigNozEditorRoleName),
|
||||
}
|
||||
|
||||
err = middleware.authzService.CheckWithTupleCreation(
|
||||
@@ -159,7 +158,7 @@ func (middleware *AuthZ) AdminAccess(next http.HandlerFunc) http.HandlerFunc {
|
||||
}
|
||||
|
||||
selectors := []authtypes.Selector{
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, roletypes.SigNozAdminRoleName),
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, authtypes.SigNozAdminRoleName),
|
||||
}
|
||||
|
||||
err = middleware.authzService.CheckWithTupleCreation(
|
||||
|
||||
@@ -9,42 +9,36 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/identn"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/usertypes"
|
||||
"golang.org/x/sync/singleflight"
|
||||
)
|
||||
|
||||
// todo: will move this in types layer with service account integration
|
||||
type apiKeyTokenKey struct{}
|
||||
|
||||
type provider struct {
|
||||
type resolver struct {
|
||||
store sqlstore.SQLStore
|
||||
config identn.Config
|
||||
headers []string
|
||||
settings factory.ScopedProviderSettings
|
||||
sfGroup *singleflight.Group
|
||||
}
|
||||
|
||||
func NewFactory(store sqlstore.SQLStore) factory.ProviderFactory[identn.IdentN, identn.Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName(authtypes.IdentNProviderAPIkey.StringValue()), func(ctx context.Context, providerSettings factory.ProviderSettings, config identn.Config) (identn.IdentN, error) {
|
||||
return New(providerSettings, store, config)
|
||||
})
|
||||
}
|
||||
|
||||
func New(providerSettings factory.ProviderSettings, store sqlstore.SQLStore, config identn.Config) (identn.IdentN, error) {
|
||||
return &provider{
|
||||
func New(providerSettings factory.ProviderSettings, store sqlstore.SQLStore, headers []string) identn.IdentN {
|
||||
return &resolver{
|
||||
store: store,
|
||||
config: config,
|
||||
headers: headers,
|
||||
settings: factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/identn/apikeyidentn"),
|
||||
sfGroup: &singleflight.Group{},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *provider) Name() authtypes.IdentNProvider {
|
||||
func (r *resolver) Name() authtypes.IdentNProvider {
|
||||
return authtypes.IdentNProviderAPIkey
|
||||
}
|
||||
|
||||
func (provider *provider) Test(req *http.Request) bool {
|
||||
for _, header := range provider.config.APIKeyConfig.Headers {
|
||||
func (r *resolver) Test(req *http.Request) bool {
|
||||
for _, header := range r.headers {
|
||||
if req.Header.Get(header) != "" {
|
||||
return true
|
||||
}
|
||||
@@ -52,12 +46,8 @@ func (provider *provider) Test(req *http.Request) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (provider *provider) Enabled() bool {
|
||||
return provider.config.APIKeyConfig.Enabled
|
||||
}
|
||||
|
||||
func (provider *provider) Pre(req *http.Request) *http.Request {
|
||||
token := provider.extractToken(req)
|
||||
func (r *resolver) Pre(req *http.Request) *http.Request {
|
||||
token := r.extractToken(req)
|
||||
if token == "" {
|
||||
return req
|
||||
}
|
||||
@@ -66,16 +56,16 @@ func (provider *provider) Pre(req *http.Request) *http.Request {
|
||||
return req.WithContext(ctx)
|
||||
}
|
||||
|
||||
func (provider *provider) GetIdentity(req *http.Request) (*authtypes.Identity, error) {
|
||||
func (r *resolver) GetIdentity(req *http.Request) (*authtypes.Identity, error) {
|
||||
ctx := req.Context()
|
||||
|
||||
apiKeyToken, ok := ctx.Value(apiKeyTokenKey{}).(string)
|
||||
if !ok || apiKeyToken == "" {
|
||||
return nil, errors.New(errors.TypeUnauthenticated, errors.CodeUnauthenticated, "missing api key")
|
||||
}
|
||||
|
||||
var apiKey types.StorableAPIKey
|
||||
err := provider.
|
||||
store.
|
||||
var apiKey usertypes.StorableAPIKey
|
||||
err := r.store.
|
||||
BunDB().
|
||||
NewSelect().
|
||||
Model(&apiKey).
|
||||
@@ -85,13 +75,12 @@ func (provider *provider) GetIdentity(req *http.Request) (*authtypes.Identity, e
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if apiKey.ExpiresAt.Before(time.Now()) && !apiKey.ExpiresAt.Equal(types.NEVER_EXPIRES) {
|
||||
if apiKey.ExpiresAt.Before(time.Now()) && !apiKey.ExpiresAt.Equal(usertypes.NEVER_EXPIRES) {
|
||||
return nil, errors.New(errors.TypeUnauthenticated, errors.CodeUnauthenticated, "api key has expired")
|
||||
}
|
||||
|
||||
var user types.User
|
||||
err = provider.
|
||||
store.
|
||||
var user usertypes.User
|
||||
err = r.store.
|
||||
BunDB().
|
||||
NewSelect().
|
||||
Model(&user).
|
||||
@@ -110,31 +99,30 @@ func (provider *provider) GetIdentity(req *http.Request) (*authtypes.Identity, e
|
||||
return &identity, nil
|
||||
}
|
||||
|
||||
func (provider *provider) Post(ctx context.Context, _ *http.Request, _ authtypes.Claims) {
|
||||
func (r *resolver) Post(ctx context.Context, _ *http.Request, _ authtypes.Claims) {
|
||||
apiKeyToken, ok := ctx.Value(apiKeyTokenKey{}).(string)
|
||||
if !ok || apiKeyToken == "" {
|
||||
return
|
||||
}
|
||||
|
||||
_, _, _ = provider.sfGroup.Do(apiKeyToken, func() (any, error) {
|
||||
_, err := provider.
|
||||
store.
|
||||
_, _, _ = r.sfGroup.Do(apiKeyToken, func() (any, error) {
|
||||
_, err := r.store.
|
||||
BunDB().
|
||||
NewUpdate().
|
||||
Model(new(types.StorableAPIKey)).
|
||||
Model(new(usertypes.StorableAPIKey)).
|
||||
Set("last_used = ?", time.Now()).
|
||||
Where("token = ?", apiKeyToken).
|
||||
Where("revoked = false").
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
provider.settings.Logger().ErrorContext(ctx, "failed to update last used of api key", "error", err)
|
||||
r.settings.Logger().ErrorContext(ctx, "failed to update last used of api key", "error", err)
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (provider *provider) extractToken(req *http.Request) string {
|
||||
for _, header := range provider.config.APIKeyConfig.Headers {
|
||||
func (r *resolver) extractToken(req *http.Request) string {
|
||||
for _, header := range r.headers {
|
||||
if v := req.Header.Get(header); v != "" {
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
package identn
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
// Config for tokenizer identN resolver
|
||||
Tokenizer TokenizerConfig `mapstructure:"tokenizer"`
|
||||
|
||||
// Config for apikey identN resolver
|
||||
APIKeyConfig APIKeyConfig `mapstructure:"apikey"`
|
||||
}
|
||||
|
||||
type TokenizerConfig struct {
|
||||
// Toggles the identN resolver
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
// Headers to extract from incoming requests
|
||||
Headers []string `mapstructure:"headers"`
|
||||
}
|
||||
|
||||
type APIKeyConfig struct {
|
||||
// Toggles the identN resolver
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
// Headers to extract from incoming requests
|
||||
Headers []string `mapstructure:"headers"`
|
||||
}
|
||||
|
||||
func NewConfigFactory() factory.ConfigFactory {
|
||||
return factory.NewConfigFactory(factory.MustNewName("identn"), newConfig)
|
||||
}
|
||||
|
||||
func newConfig() factory.Config {
|
||||
return &Config{
|
||||
Tokenizer: TokenizerConfig{
|
||||
Enabled: true,
|
||||
Headers: []string{"Authorization", "Sec-WebSocket-Protocol"},
|
||||
},
|
||||
APIKeyConfig: APIKeyConfig{
|
||||
Enabled: true,
|
||||
Headers: []string{"SIGNOZ-API-KEY"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c Config) Validate() error {
|
||||
return nil
|
||||
}
|
||||
@@ -23,8 +23,6 @@ type IdentN interface {
|
||||
GetIdentity(r *http.Request) (*authtypes.Identity, error)
|
||||
|
||||
Name() authtypes.IdentNProvider
|
||||
|
||||
Enabled() bool
|
||||
}
|
||||
|
||||
// IdentNWithPreHook is optionally implemented by resolvers that need to
|
||||
|
||||
@@ -12,16 +12,8 @@ type identNResolver struct {
|
||||
}
|
||||
|
||||
func NewIdentNResolver(providerSettings factory.ProviderSettings, identNs ...IdentN) IdentNResolver {
|
||||
enabledIdentNs := []IdentN{}
|
||||
|
||||
for _, identN := range identNs {
|
||||
if identN.Enabled() {
|
||||
enabledIdentNs = append(enabledIdentNs, identN)
|
||||
}
|
||||
}
|
||||
|
||||
return &identNResolver{
|
||||
identNs: enabledIdentNs,
|
||||
identNs: identNs,
|
||||
settings: factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/identn"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,34 +13,28 @@ import (
|
||||
"golang.org/x/sync/singleflight"
|
||||
)
|
||||
|
||||
type provider struct {
|
||||
type resolver struct {
|
||||
tokenizer tokenizer.Tokenizer
|
||||
config identn.Config
|
||||
headers []string
|
||||
settings factory.ScopedProviderSettings
|
||||
sfGroup *singleflight.Group
|
||||
}
|
||||
|
||||
func NewFactory(tokenizer tokenizer.Tokenizer) factory.ProviderFactory[identn.IdentN, identn.Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName(authtypes.IdentNProviderTokenizer.StringValue()), func(ctx context.Context, providerSettings factory.ProviderSettings, config identn.Config) (identn.IdentN, error) {
|
||||
return New(providerSettings, tokenizer, config)
|
||||
})
|
||||
}
|
||||
|
||||
func New(providerSettings factory.ProviderSettings, tokenizer tokenizer.Tokenizer, config identn.Config) (identn.IdentN, error) {
|
||||
return &provider{
|
||||
func New(providerSettings factory.ProviderSettings, tokenizer tokenizer.Tokenizer, headers []string) identn.IdentN {
|
||||
return &resolver{
|
||||
tokenizer: tokenizer,
|
||||
config: config,
|
||||
headers: headers,
|
||||
settings: factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/identn/tokenizeridentn"),
|
||||
sfGroup: &singleflight.Group{},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *provider) Name() authtypes.IdentNProvider {
|
||||
func (r *resolver) Name() authtypes.IdentNProvider {
|
||||
return authtypes.IdentNProviderTokenizer
|
||||
}
|
||||
|
||||
func (provider *provider) Test(req *http.Request) bool {
|
||||
for _, header := range provider.config.Tokenizer.Headers {
|
||||
func (r *resolver) Test(req *http.Request) bool {
|
||||
for _, header := range r.headers {
|
||||
if req.Header.Get(header) != "" {
|
||||
return true
|
||||
}
|
||||
@@ -48,12 +42,8 @@ func (provider *provider) Test(req *http.Request) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (provider *provider) Enabled() bool {
|
||||
return provider.config.Tokenizer.Enabled
|
||||
}
|
||||
|
||||
func (provider *provider) Pre(req *http.Request) *http.Request {
|
||||
accessToken := provider.extractToken(req)
|
||||
func (r *resolver) Pre(req *http.Request) *http.Request {
|
||||
accessToken := r.extractToken(req)
|
||||
if accessToken == "" {
|
||||
return req
|
||||
}
|
||||
@@ -62,7 +52,7 @@ func (provider *provider) Pre(req *http.Request) *http.Request {
|
||||
return req.WithContext(ctx)
|
||||
}
|
||||
|
||||
func (provider *provider) GetIdentity(req *http.Request) (*authtypes.Identity, error) {
|
||||
func (r *resolver) GetIdentity(req *http.Request) (*authtypes.Identity, error) {
|
||||
ctx := req.Context()
|
||||
|
||||
accessToken, err := authtypes.AccessTokenFromContext(ctx)
|
||||
@@ -70,45 +60,41 @@ func (provider *provider) GetIdentity(req *http.Request) (*authtypes.Identity, e
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return provider.tokenizer.GetIdentity(ctx, accessToken)
|
||||
return r.tokenizer.GetIdentity(ctx, accessToken)
|
||||
}
|
||||
|
||||
func (provider *provider) Post(ctx context.Context, _ *http.Request, _ authtypes.Claims) {
|
||||
if !provider.config.Tokenizer.Enabled {
|
||||
return
|
||||
}
|
||||
|
||||
func (r *resolver) Post(ctx context.Context, _ *http.Request, _ authtypes.Claims) {
|
||||
accessToken, err := authtypes.AccessTokenFromContext(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, _, _ = provider.sfGroup.Do(accessToken, func() (any, error) {
|
||||
if err := provider.tokenizer.SetLastObservedAt(ctx, accessToken, time.Now()); err != nil {
|
||||
provider.settings.Logger().ErrorContext(ctx, "failed to set last observed at", "error", err)
|
||||
_, _, _ = r.sfGroup.Do(accessToken, func() (any, error) {
|
||||
if err := r.tokenizer.SetLastObservedAt(ctx, accessToken, time.Now()); err != nil {
|
||||
r.settings.Logger().ErrorContext(ctx, "failed to set last observed at", "error", err)
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (provider *provider) extractToken(req *http.Request) string {
|
||||
func (r *resolver) extractToken(req *http.Request) string {
|
||||
var value string
|
||||
for _, header := range provider.config.Tokenizer.Headers {
|
||||
for _, header := range r.headers {
|
||||
if v := req.Header.Get(header); v != "" {
|
||||
value = v
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
accessToken, ok := provider.parseBearerAuth(value)
|
||||
accessToken, ok := r.parseBearerAuth(value)
|
||||
if !ok {
|
||||
return value
|
||||
}
|
||||
return accessToken
|
||||
}
|
||||
|
||||
func (provider *provider) parseBearerAuth(auth string) (string, bool) {
|
||||
func (r *resolver) parseBearerAuth(auth string) (string, bool) {
|
||||
const prefix = "Bearer "
|
||||
if len(auth) < len(prefix) || !strings.EqualFold(auth[:len(prefix)], prefix) {
|
||||
return "", false
|
||||
|
||||
@@ -43,7 +43,7 @@ type Module interface {
|
||||
|
||||
Update(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, data dashboardtypes.UpdatableDashboard, diff int) (*dashboardtypes.Dashboard, error)
|
||||
|
||||
LockUnlock(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, role types.Role, lock bool) error
|
||||
LockUnlock(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, role authtypes.LegacyRole, lock bool) error
|
||||
|
||||
Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ func (module *module) Update(ctx context.Context, orgID valuer.UUID, id valuer.U
|
||||
return dashboard, nil
|
||||
}
|
||||
|
||||
func (module *module) LockUnlock(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, role types.Role, lock bool) error {
|
||||
func (module *module) LockUnlock(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, role authtypes.LegacyRole, lock bool) error {
|
||||
dashboard, err := module.Get(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/tokenizer"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/usertypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
@@ -66,7 +67,7 @@ func (module *module) GetSessionContext(ctx context.Context, email valuer.Email,
|
||||
}
|
||||
|
||||
// filter out deleted users
|
||||
users = slices.DeleteFunc(users, func(user *types.User) bool { return user.ErrIfDeleted() != nil })
|
||||
users = slices.DeleteFunc(users, func(user *usertypes.User) bool { return user.ErrIfDeleted() != nil })
|
||||
|
||||
// Since email is a valuer, we can be sure that it is a valid email and we can split it to get the domain name.
|
||||
name := strings.Split(email.String(), "@")[1]
|
||||
@@ -144,7 +145,7 @@ func (module *module) CreateCallbackAuthNSession(ctx context.Context, authNProvi
|
||||
roleMapping := authDomain.AuthDomainConfig().RoleMapping
|
||||
role := roleMapping.NewRoleFromCallbackIdentity(callbackIdentity)
|
||||
|
||||
user, err := types.NewUser(callbackIdentity.Name, callbackIdentity.Email, role, callbackIdentity.OrgID, types.UserStatusActive)
|
||||
user, err := usertypes.NewUser(callbackIdentity.Name, callbackIdentity.Email, role, callbackIdentity.OrgID, usertypes.UserStatusActive)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/tracefunnel"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
traceFunnels "github.com/SigNoz/signoz/pkg/types/tracefunneltypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/usertypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
@@ -30,7 +31,7 @@ func (module *module) Create(ctx context.Context, timestamp int64, name string,
|
||||
funnel.CreatedBy = userID.String()
|
||||
|
||||
// Set up the user relationship
|
||||
funnel.CreatedByUser = &types.User{
|
||||
funnel.CreatedByUser = &usertypes.User{
|
||||
Identifiable: types.Identifiable{
|
||||
ID: userID,
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/usertypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
@@ -68,7 +68,7 @@ func (c Config) Validate() error {
|
||||
if c.Root.Password == "" {
|
||||
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "user::root::password is required when root user is enabled")
|
||||
}
|
||||
if !types.IsPasswordValid(c.Root.Password) {
|
||||
if !usertypes.IsPasswordValid(c.Root.Password) {
|
||||
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "user::root::password does not meet password requirements")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,25 +6,25 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/flagger"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/usertypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type getter struct {
|
||||
store types.UserStore
|
||||
store usertypes.UserStore
|
||||
flagger flagger.Flagger
|
||||
}
|
||||
|
||||
func NewGetter(store types.UserStore, flagger flagger.Flagger) user.Getter {
|
||||
func NewGetter(store usertypes.UserStore, flagger flagger.Flagger) user.Getter {
|
||||
return &getter{store: store, flagger: flagger}
|
||||
}
|
||||
|
||||
func (module *getter) GetRootUserByOrgID(ctx context.Context, orgID valuer.UUID) (*types.User, error) {
|
||||
func (module *getter) GetRootUserByOrgID(ctx context.Context, orgID valuer.UUID) (*usertypes.User, error) {
|
||||
return module.store.GetRootUserByOrgID(ctx, orgID)
|
||||
}
|
||||
|
||||
func (module *getter) ListByOrgID(ctx context.Context, orgID valuer.UUID) ([]*types.User, error) {
|
||||
func (module *getter) ListByOrgID(ctx context.Context, orgID valuer.UUID) ([]*usertypes.User, error) {
|
||||
users, err := module.store.ListUsersByOrgID(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -35,13 +35,13 @@ func (module *getter) ListByOrgID(ctx context.Context, orgID valuer.UUID) ([]*ty
|
||||
hideRootUsers := module.flagger.BooleanOrEmpty(ctx, flagger.FeatureHideRootUser, evalCtx)
|
||||
|
||||
if hideRootUsers {
|
||||
users = slices.DeleteFunc(users, func(user *types.User) bool { return user.IsRoot })
|
||||
users = slices.DeleteFunc(users, func(user *usertypes.User) bool { return user.IsRoot })
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (module *getter) GetUsersByEmail(ctx context.Context, email valuer.Email) ([]*types.User, error) {
|
||||
func (module *getter) GetUsersByEmail(ctx context.Context, email valuer.Email) ([]*usertypes.User, error) {
|
||||
users, err := module.store.GetUsersByEmail(ctx, email)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -50,7 +50,7 @@ func (module *getter) GetUsersByEmail(ctx context.Context, email valuer.Email) (
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (module *getter) GetByOrgIDAndID(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*types.User, error) {
|
||||
func (module *getter) GetByOrgIDAndID(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*usertypes.User, error) {
|
||||
user, err := module.store.GetByOrgIDAndID(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -59,7 +59,7 @@ func (module *getter) GetByOrgIDAndID(ctx context.Context, orgID valuer.UUID, id
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (module *getter) Get(ctx context.Context, id valuer.UUID) (*types.User, error) {
|
||||
func (module *getter) Get(ctx context.Context, id valuer.UUID) (*usertypes.User, error) {
|
||||
user, err := module.store.GetUser(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -68,7 +68,7 @@ func (module *getter) Get(ctx context.Context, id valuer.UUID) (*types.User, err
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (module *getter) ListUsersByEmailAndOrgIDs(ctx context.Context, email valuer.Email, orgIDs []valuer.UUID) ([]*types.User, error) {
|
||||
func (module *getter) ListUsersByEmailAndOrgIDs(ctx context.Context, email valuer.Email, orgIDs []valuer.UUID) ([]*usertypes.User, error) {
|
||||
users, err := module.store.ListUsersByEmailAndOrgIDs(ctx, email, orgIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -95,7 +95,7 @@ func (module *getter) CountByOrgIDAndStatuses(ctx context.Context, orgID valuer.
|
||||
return counts, nil
|
||||
}
|
||||
|
||||
func (module *getter) GetFactorPasswordByUserID(ctx context.Context, userID valuer.UUID) (*types.FactorPassword, error) {
|
||||
func (module *getter) GetFactorPasswordByUserID(ctx context.Context, userID valuer.UUID) (*usertypes.FactorPassword, error) {
|
||||
factorPassword, err := module.store.GetPasswordByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -103,3 +103,12 @@ func (module *getter) GetFactorPasswordByUserID(ctx context.Context, userID valu
|
||||
|
||||
return factorPassword, nil
|
||||
}
|
||||
|
||||
func (module *getter) GetActiveUserAndFactorPasswordByEmailAndOrgID(ctx context.Context, email string, orgID valuer.UUID) (*usertypes.User, *usertypes.FactorPassword, error) {
|
||||
user, factorPassword, err := module.store.GetActiveUserAndFactorPasswordByEmailAndOrgID(ctx, email, orgID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return user, factorPassword, nil
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/http/binding"
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
root "github.com/SigNoz/signoz/pkg/modules/user"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/integrationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/usertypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
@@ -31,7 +31,7 @@ func (h *handler) AcceptInvite(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
req := new(types.PostableAcceptInvite)
|
||||
req := new(usertypes.PostableAcceptInvite)
|
||||
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
@@ -56,14 +56,14 @@ func (h *handler) CreateInvite(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
var req types.PostableInvite
|
||||
var req usertypes.PostableInvite
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
invites, err := h.module.CreateBulkInvite(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(claims.UserID), &types.PostableBulkInviteRequest{
|
||||
Invites: []types.PostableInvite{req},
|
||||
invites, err := h.module.CreateBulkInvite(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(claims.UserID), &usertypes.PostableBulkInviteRequest{
|
||||
Invites: []usertypes.PostableInvite{req},
|
||||
})
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
@@ -83,7 +83,7 @@ func (h *handler) CreateBulkInvite(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
var req types.PostableBulkInviteRequest
|
||||
var req usertypes.PostableBulkInviteRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
@@ -214,7 +214,7 @@ func (h *handler) ListUsers(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// temp code - show only active users
|
||||
users = slices.DeleteFunc(users, func(user *types.User) bool { return user.Status != types.UserStatusActive })
|
||||
users = slices.DeleteFunc(users, func(user *usertypes.User) bool { return user.Status != usertypes.UserStatusActive })
|
||||
|
||||
render.Success(w, http.StatusOK, users)
|
||||
}
|
||||
@@ -231,7 +231,7 @@ func (h *handler) UpdateUser(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
var user types.User
|
||||
var user usertypes.User
|
||||
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
@@ -297,7 +297,7 @@ func (handler *handler) ResetPassword(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
req := new(types.PostableResetPassword)
|
||||
req := new(usertypes.PostableResetPassword)
|
||||
if err := json.NewDecoder(r.Body).Decode(req); err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
@@ -316,7 +316,7 @@ func (handler *handler) ChangePassword(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var req types.ChangePasswordRequest
|
||||
var req usertypes.ChangePasswordRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
@@ -335,7 +335,7 @@ func (h *handler) ForgotPassword(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
req := new(types.PostableForgotPassword)
|
||||
req := new(usertypes.PostableForgotPassword)
|
||||
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
@@ -360,13 +360,13 @@ func (h *handler) CreateAPIKey(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
req := new(types.PostableAPIKey)
|
||||
req := new(usertypes.PostableAPIKey)
|
||||
if err := json.NewDecoder(r.Body).Decode(req); err != nil {
|
||||
render.Error(w, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to decode api key"))
|
||||
return
|
||||
}
|
||||
|
||||
apiKey, err := types.NewStorableAPIKey(
|
||||
apiKey, err := usertypes.NewStorableAPIKey(
|
||||
req.Name,
|
||||
valuer.MustNewUUID(claims.UserID),
|
||||
req.Role,
|
||||
@@ -411,13 +411,13 @@ func (h *handler) ListAPIKeys(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// for backward compatibility
|
||||
if len(apiKeys) == 0 {
|
||||
render.Success(w, http.StatusOK, []types.GettableAPIKey{})
|
||||
render.Success(w, http.StatusOK, []usertypes.GettableAPIKey{})
|
||||
return
|
||||
}
|
||||
|
||||
result := make([]*types.GettableAPIKey, len(apiKeys))
|
||||
result := make([]*usertypes.GettableAPIKey, len(apiKeys))
|
||||
for i, apiKey := range apiKeys {
|
||||
result[i] = types.NewGettableAPIKeyFromStorableAPIKey(apiKey)
|
||||
result[i] = usertypes.NewGettableAPIKeyFromStorableAPIKey(apiKey)
|
||||
}
|
||||
|
||||
render.Success(w, http.StatusOK, result)
|
||||
@@ -434,7 +434,7 @@ func (h *handler) UpdateAPIKey(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
req := types.StorableAPIKey{}
|
||||
req := usertypes.StorableAPIKey{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
render.Error(w, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to decode api key"))
|
||||
return
|
||||
|
||||
@@ -19,13 +19,13 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/emailtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/integrationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/usertypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/dustin/go-humanize"
|
||||
)
|
||||
|
||||
type Module struct {
|
||||
store types.UserStore
|
||||
store usertypes.UserStore
|
||||
tokenizer tokenizer.Tokenizer
|
||||
emailing emailing.Emailing
|
||||
settings factory.ScopedProviderSettings
|
||||
@@ -36,7 +36,7 @@ type Module struct {
|
||||
}
|
||||
|
||||
// This module is a WIP, don't take inspiration from this.
|
||||
func NewModule(store types.UserStore, tokenizer tokenizer.Tokenizer, emailing emailing.Emailing, providerSettings factory.ProviderSettings, orgSetter organization.Setter, authz authz.AuthZ, analytics analytics.Analytics, config user.Config) root.Module {
|
||||
func NewModule(store usertypes.UserStore, tokenizer tokenizer.Tokenizer, emailing emailing.Emailing, providerSettings factory.ProviderSettings, orgSetter organization.Setter, authz authz.AuthZ, analytics analytics.Analytics, config user.Config) root.Module {
|
||||
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/modules/user/impluser")
|
||||
return &Module{
|
||||
store: store,
|
||||
@@ -50,7 +50,7 @@ func NewModule(store types.UserStore, tokenizer tokenizer.Tokenizer, emailing em
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Module) AcceptInvite(ctx context.Context, token string, password string) (*types.User, error) {
|
||||
func (m *Module) AcceptInvite(ctx context.Context, token string, password string) (*usertypes.User, error) {
|
||||
// get the user by reset password token
|
||||
user, err := m.store.GetUserByResetPasswordToken(ctx, token)
|
||||
if err != nil {
|
||||
@@ -72,7 +72,7 @@ func (m *Module) AcceptInvite(ctx context.Context, token string, password string
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (m *Module) GetInviteByToken(ctx context.Context, token string) (*types.Invite, error) {
|
||||
func (m *Module) GetInviteByToken(ctx context.Context, token string) (*usertypes.Invite, error) {
|
||||
// get the user
|
||||
user, err := m.store.GetUserByResetPasswordToken(ctx, token)
|
||||
if err != nil {
|
||||
@@ -80,7 +80,7 @@ func (m *Module) GetInviteByToken(ctx context.Context, token string) (*types.Inv
|
||||
}
|
||||
|
||||
// create a dummy invite obj for backward compatibility
|
||||
invite := &types.Invite{
|
||||
invite := &usertypes.Invite{
|
||||
Identifiable: types.Identifiable{
|
||||
ID: user.ID,
|
||||
},
|
||||
@@ -99,7 +99,7 @@ func (m *Module) GetInviteByToken(ctx context.Context, token string) (*types.Inv
|
||||
}
|
||||
|
||||
// CreateBulk implements invite.Module.
|
||||
func (m *Module) CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID valuer.UUID, bulkInvites *types.PostableBulkInviteRequest) ([]*types.Invite, error) {
|
||||
func (m *Module) CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID valuer.UUID, bulkInvites *usertypes.PostableBulkInviteRequest) ([]*usertypes.Invite, error) {
|
||||
creator, err := m.store.GetUser(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -110,7 +110,7 @@ func (m *Module) CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID
|
||||
for idx, invite := range bulkInvites.Invites {
|
||||
emails[idx] = invite.Email.StringValue()
|
||||
}
|
||||
users, err := m.store.GetUsersByEmailsOrgIDAndStatuses(ctx, orgID, emails, []string{types.UserStatusActive.StringValue(), types.UserStatusPendingInvite.StringValue()})
|
||||
users, err := m.store.GetUsersByEmailsOrgIDAndStatuses(ctx, orgID, emails, []string{usertypes.UserStatusActive.StringValue(), usertypes.UserStatusPendingInvite.StringValue()})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -120,7 +120,7 @@ func (m *Module) CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID
|
||||
return nil, errors.WithAdditionalf(err, "Cannot send invite to root user")
|
||||
}
|
||||
|
||||
if users[0].Status == types.UserStatusPendingInvite {
|
||||
if users[0].Status == usertypes.UserStatusPendingInvite {
|
||||
return nil, errors.Newf(errors.TypeAlreadyExists, errors.CodeAlreadyExists, "An invite already exists for this email: %s", users[0].Email.StringValue())
|
||||
}
|
||||
|
||||
@@ -128,21 +128,21 @@ func (m *Module) CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID
|
||||
}
|
||||
|
||||
type userWithResetToken struct {
|
||||
User *types.User
|
||||
ResetPasswordToken *types.ResetPasswordToken
|
||||
User *usertypes.User
|
||||
ResetPasswordToken *usertypes.ResetPasswordToken
|
||||
}
|
||||
|
||||
newUsersWithResetToken := make([]*userWithResetToken, len(bulkInvites.Invites))
|
||||
|
||||
if err := m.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
for idx, invite := range bulkInvites.Invites {
|
||||
role, err := types.NewRole(invite.Role.String())
|
||||
role, err := authtypes.NewLegacyRole(invite.Role.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create a new user with pending invite status
|
||||
newUser, err := types.NewUser(invite.Name, invite.Email, role, orgID, types.UserStatusPendingInvite)
|
||||
newUser, err := usertypes.NewUser(invite.Name, invite.Email, role, orgID, usertypes.UserStatusPendingInvite)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -170,7 +170,7 @@ func (m *Module) CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID
|
||||
return nil, err
|
||||
}
|
||||
|
||||
invites := make([]*types.Invite, len(bulkInvites.Invites))
|
||||
invites := make([]*usertypes.Invite, len(bulkInvites.Invites))
|
||||
|
||||
// send password reset emails to all the invited users
|
||||
for idx, userWithToken := range newUsersWithResetToken {
|
||||
@@ -179,7 +179,7 @@ func (m *Module) CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID
|
||||
"invitee_role": userWithToken.User.Role,
|
||||
})
|
||||
|
||||
invite := &types.Invite{
|
||||
invite := &usertypes.Invite{
|
||||
Identifiable: types.Identifiable{
|
||||
ID: userWithToken.User.ID,
|
||||
},
|
||||
@@ -219,16 +219,16 @@ func (m *Module) CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID
|
||||
return invites, nil
|
||||
}
|
||||
|
||||
func (m *Module) ListInvite(ctx context.Context, orgID string) ([]*types.Invite, error) {
|
||||
func (m *Module) ListInvite(ctx context.Context, orgID string) ([]*usertypes.Invite, error) {
|
||||
// find all the users with pending_invite status
|
||||
users, err := m.store.ListUsersByOrgID(ctx, valuer.MustNewUUID(orgID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pendingUsers := slices.DeleteFunc(users, func(user *types.User) bool { return user.Status != types.UserStatusPendingInvite })
|
||||
pendingUsers := slices.DeleteFunc(users, func(user *usertypes.User) bool { return user.Status != usertypes.UserStatusPendingInvite })
|
||||
|
||||
var invites []*types.Invite
|
||||
var invites []*usertypes.Invite
|
||||
|
||||
for _, pUser := range pendingUsers {
|
||||
// get the reset password token
|
||||
@@ -238,7 +238,7 @@ func (m *Module) ListInvite(ctx context.Context, orgID string) ([]*types.Invite,
|
||||
}
|
||||
|
||||
// create a dummy invite obj for backward compatibility
|
||||
invite := &types.Invite{
|
||||
invite := &usertypes.Invite{
|
||||
Identifiable: types.Identifiable{
|
||||
ID: pUser.ID,
|
||||
},
|
||||
@@ -259,11 +259,11 @@ func (m *Module) ListInvite(ctx context.Context, orgID string) ([]*types.Invite,
|
||||
return invites, nil
|
||||
}
|
||||
|
||||
func (module *Module) CreateUser(ctx context.Context, input *types.User, opts ...root.CreateUserOption) error {
|
||||
func (module *Module) CreateUser(ctx context.Context, input *usertypes.User, opts ...root.CreateUserOption) error {
|
||||
createUserOpts := root.NewCreateUserOptions(opts...)
|
||||
|
||||
// since assign is idempotant multiple calls to assign won't cause issues in case of retries.
|
||||
err := module.authz.Grant(ctx, input.OrgID, []string{roletypes.MustGetSigNozManagedRoleFromExistingRole(input.Role)}, authtypes.MustNewSubject(authtypes.TypeableUser, input.ID.StringValue(), input.OrgID, nil))
|
||||
err := module.authz.Grant(ctx, input.OrgID, []string{authtypes.MustGetSigNozManagedRoleFromExistingRole(input.Role)}, authtypes.MustNewSubject(authtypes.TypeableUser, input.ID.StringValue(), input.OrgID, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -284,14 +284,14 @@ func (module *Module) CreateUser(ctx context.Context, input *types.User, opts ..
|
||||
return err
|
||||
}
|
||||
|
||||
traitsOrProperties := types.NewTraitsFromUser(input)
|
||||
traitsOrProperties := usertypes.NewTraitsFromUser(input)
|
||||
module.analytics.IdentifyUser(ctx, input.OrgID.String(), input.ID.String(), traitsOrProperties)
|
||||
module.analytics.TrackUser(ctx, input.OrgID.String(), input.ID.String(), "User Created", traitsOrProperties)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Module) UpdateUser(ctx context.Context, orgID valuer.UUID, id string, user *types.User, updatedBy string) (*types.User, error) {
|
||||
func (m *Module) UpdateUser(ctx context.Context, orgID valuer.UUID, id string, user *usertypes.User, updatedBy string) (*usertypes.User, error) {
|
||||
existingUser, err := m.store.GetUser(ctx, valuer.MustNewUUID(id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -314,13 +314,13 @@ func (m *Module) UpdateUser(ctx context.Context, orgID valuer.UUID, id string, u
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if user.Role != "" && user.Role != existingUser.Role && requestor.Role != types.RoleAdmin {
|
||||
if user.Role != "" && user.Role != existingUser.Role && requestor.Role != authtypes.RoleAdmin {
|
||||
return nil, errors.New(errors.TypeForbidden, errors.CodeForbidden, "only admins can change roles")
|
||||
}
|
||||
|
||||
// Make sure that the request is not demoting the last admin user.
|
||||
if user.Role != "" && user.Role != existingUser.Role && existingUser.Role == types.RoleAdmin {
|
||||
adminUsers, err := m.store.GetActiveUsersByRoleAndOrgID(ctx, types.RoleAdmin, orgID)
|
||||
if user.Role != "" && user.Role != existingUser.Role && existingUser.Role == authtypes.RoleAdmin {
|
||||
adminUsers, err := m.store.GetActiveUsersByRoleAndOrgID(ctx, authtypes.RoleAdmin, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -333,8 +333,8 @@ func (m *Module) UpdateUser(ctx context.Context, orgID valuer.UUID, id string, u
|
||||
if user.Role != "" && user.Role != existingUser.Role {
|
||||
err = m.authz.ModifyGrant(ctx,
|
||||
orgID,
|
||||
[]string{roletypes.MustGetSigNozManagedRoleFromExistingRole(existingUser.Role)},
|
||||
[]string{roletypes.MustGetSigNozManagedRoleFromExistingRole(user.Role)},
|
||||
[]string{authtypes.MustGetSigNozManagedRoleFromExistingRole(existingUser.Role)},
|
||||
[]string{authtypes.MustGetSigNozManagedRoleFromExistingRole(user.Role)},
|
||||
authtypes.MustNewSubject(authtypes.TypeableUser, id, orgID, nil),
|
||||
)
|
||||
if err != nil {
|
||||
@@ -350,12 +350,12 @@ func (m *Module) UpdateUser(ctx context.Context, orgID valuer.UUID, id string, u
|
||||
return existingUser, nil
|
||||
}
|
||||
|
||||
func (module *Module) UpdateAnyUser(ctx context.Context, orgID valuer.UUID, user *types.User) error {
|
||||
func (module *Module) UpdateAnyUser(ctx context.Context, orgID valuer.UUID, user *usertypes.User) error {
|
||||
if err := module.store.UpdateUser(ctx, orgID, user); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
traits := types.NewTraitsFromUser(user)
|
||||
traits := usertypes.NewTraitsFromUser(user)
|
||||
module.analytics.IdentifyUser(ctx, user.OrgID.String(), user.ID.String(), traits)
|
||||
module.analytics.TrackUser(ctx, user.OrgID.String(), user.ID.String(), "User Updated", traits)
|
||||
|
||||
@@ -385,17 +385,17 @@ func (module *Module) DeleteUser(ctx context.Context, orgID valuer.UUID, id stri
|
||||
}
|
||||
|
||||
// don't allow to delete the last admin user
|
||||
adminUsers, err := module.store.GetActiveUsersByRoleAndOrgID(ctx, types.RoleAdmin, orgID)
|
||||
adminUsers, err := module.store.GetActiveUsersByRoleAndOrgID(ctx, authtypes.RoleAdmin, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(adminUsers) == 1 && user.Role == types.RoleAdmin {
|
||||
if len(adminUsers) == 1 && user.Role == authtypes.RoleAdmin {
|
||||
return errors.New(errors.TypeForbidden, errors.CodeForbidden, "cannot delete the last admin")
|
||||
}
|
||||
|
||||
// since revoke is idempotant multiple calls to revoke won't cause issues in case of retries
|
||||
err = module.authz.Revoke(ctx, orgID, []string{roletypes.MustGetSigNozManagedRoleFromExistingRole(user.Role)}, authtypes.MustNewSubject(authtypes.TypeableUser, id, orgID, nil))
|
||||
err = module.authz.Revoke(ctx, orgID, []string{authtypes.MustGetSigNozManagedRoleFromExistingRole(user.Role)}, authtypes.MustNewSubject(authtypes.TypeableUser, id, orgID, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -412,7 +412,7 @@ func (module *Module) DeleteUser(ctx context.Context, orgID valuer.UUID, id stri
|
||||
return nil
|
||||
}
|
||||
|
||||
func (module *Module) GetOrCreateResetPasswordToken(ctx context.Context, userID valuer.UUID) (*types.ResetPasswordToken, error) {
|
||||
func (module *Module) GetOrCreateResetPasswordToken(ctx context.Context, userID valuer.UUID) (*usertypes.ResetPasswordToken, error) {
|
||||
user, err := module.store.GetUser(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -435,7 +435,7 @@ func (module *Module) GetOrCreateResetPasswordToken(ctx context.Context, userID
|
||||
|
||||
if password == nil {
|
||||
// if the user does not have a password, we need to create a new one (common for SSO/SAML users)
|
||||
password = types.MustGenerateFactorPassword(userID.String())
|
||||
password = usertypes.MustGenerateFactorPassword(userID.String())
|
||||
|
||||
if err := module.store.CreatePassword(ctx, password); err != nil {
|
||||
return nil, err
|
||||
@@ -461,7 +461,7 @@ func (module *Module) GetOrCreateResetPasswordToken(ctx context.Context, userID
|
||||
}
|
||||
|
||||
// create a new token
|
||||
resetPasswordToken, err := types.NewResetPasswordToken(password.ID, time.Now().Add(module.config.Password.Reset.MaxTokenLifetime))
|
||||
resetPasswordToken, err := usertypes.NewResetPasswordToken(password.ID, time.Now().Add(module.config.Password.Reset.MaxTokenLifetime))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -554,11 +554,11 @@ func (module *Module) UpdatePasswordByResetPasswordToken(ctx context.Context, to
|
||||
}
|
||||
|
||||
// since grant is idempotent, multiple calls won't cause issues in case of retries
|
||||
if user.Status == types.UserStatusPendingInvite {
|
||||
if user.Status == usertypes.UserStatusPendingInvite {
|
||||
if err = module.authz.Grant(
|
||||
ctx,
|
||||
user.OrgID,
|
||||
[]string{roletypes.MustGetSigNozManagedRoleFromExistingRole(user.Role)},
|
||||
[]string{authtypes.MustGetSigNozManagedRoleFromExistingRole(user.Role)},
|
||||
authtypes.MustNewSubject(authtypes.TypeableUser, user.ID.StringValue(), user.OrgID, nil),
|
||||
); err != nil {
|
||||
return err
|
||||
@@ -566,8 +566,8 @@ func (module *Module) UpdatePasswordByResetPasswordToken(ctx context.Context, to
|
||||
}
|
||||
|
||||
return module.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
if user.Status == types.UserStatusPendingInvite {
|
||||
if err := user.UpdateStatus(types.UserStatusActive); err != nil {
|
||||
if user.Status == usertypes.UserStatusPendingInvite {
|
||||
if err := user.UpdateStatus(usertypes.UserStatusActive); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := module.store.UpdateUser(ctx, user.OrgID, user); err != nil {
|
||||
@@ -607,7 +607,7 @@ func (module *Module) UpdatePassword(ctx context.Context, userID valuer.UUID, ol
|
||||
}
|
||||
|
||||
if !password.Equals(oldpasswd) {
|
||||
return errors.New(errors.TypeInvalidInput, types.ErrCodeIncorrectPassword, "old password is incorrect")
|
||||
return errors.New(errors.TypeInvalidInput, usertypes.ErrCodeIncorrectPassword, "old password is incorrect")
|
||||
}
|
||||
|
||||
if err := password.Update(passwd); err != nil {
|
||||
@@ -631,7 +631,7 @@ func (module *Module) UpdatePassword(ctx context.Context, userID valuer.UUID, ol
|
||||
return module.tokenizer.DeleteTokensByUserID(ctx, userID)
|
||||
}
|
||||
|
||||
func (module *Module) GetOrCreateUser(ctx context.Context, user *types.User, opts ...root.CreateUserOption) (*types.User, error) {
|
||||
func (module *Module) GetOrCreateUser(ctx context.Context, user *usertypes.User, opts ...root.CreateUserOption) (*usertypes.User, error) {
|
||||
existingUser, err := module.GetNonDeletedUserByEmailAndOrgID(ctx, user.Email, user.OrgID)
|
||||
if err != nil {
|
||||
if !errors.Ast(err, errors.TypeNotFound) {
|
||||
@@ -641,7 +641,7 @@ func (module *Module) GetOrCreateUser(ctx context.Context, user *types.User, opt
|
||||
|
||||
if existingUser != nil {
|
||||
// for users logging through SSO flow but are having status as pending_invite
|
||||
if existingUser.Status == types.UserStatusPendingInvite {
|
||||
if existingUser.Status == usertypes.UserStatusPendingInvite {
|
||||
// respect the role coming from the SSO
|
||||
existingUser.Update("", user.Role)
|
||||
// activate the user
|
||||
@@ -661,19 +661,19 @@ func (module *Module) GetOrCreateUser(ctx context.Context, user *types.User, opt
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (m *Module) CreateAPIKey(ctx context.Context, apiKey *types.StorableAPIKey) error {
|
||||
func (m *Module) CreateAPIKey(ctx context.Context, apiKey *usertypes.StorableAPIKey) error {
|
||||
return m.store.CreateAPIKey(ctx, apiKey)
|
||||
}
|
||||
|
||||
func (m *Module) UpdateAPIKey(ctx context.Context, id valuer.UUID, apiKey *types.StorableAPIKey, updaterID valuer.UUID) error {
|
||||
func (m *Module) UpdateAPIKey(ctx context.Context, id valuer.UUID, apiKey *usertypes.StorableAPIKey, updaterID valuer.UUID) error {
|
||||
return m.store.UpdateAPIKey(ctx, id, apiKey, updaterID)
|
||||
}
|
||||
|
||||
func (m *Module) ListAPIKeys(ctx context.Context, orgID valuer.UUID) ([]*types.StorableAPIKeyUser, error) {
|
||||
func (m *Module) ListAPIKeys(ctx context.Context, orgID valuer.UUID) ([]*usertypes.StorableAPIKeyUser, error) {
|
||||
return m.store.ListAPIKeys(ctx, orgID)
|
||||
}
|
||||
|
||||
func (m *Module) GetAPIKey(ctx context.Context, orgID, id valuer.UUID) (*types.StorableAPIKeyUser, error) {
|
||||
func (m *Module) GetAPIKey(ctx context.Context, orgID, id valuer.UUID) (*usertypes.StorableAPIKeyUser, error) {
|
||||
return m.store.GetAPIKey(ctx, orgID, id)
|
||||
}
|
||||
|
||||
@@ -681,18 +681,18 @@ func (m *Module) RevokeAPIKey(ctx context.Context, id, removedByUserID valuer.UU
|
||||
return m.store.RevokeAPIKey(ctx, id, removedByUserID)
|
||||
}
|
||||
|
||||
func (module *Module) CreateFirstUser(ctx context.Context, organization *types.Organization, name string, email valuer.Email, passwd string) (*types.User, error) {
|
||||
user, err := types.NewRootUser(name, email, organization.ID)
|
||||
func (module *Module) CreateFirstUser(ctx context.Context, organization *types.Organization, name string, email valuer.Email, passwd string) (*usertypes.User, error) {
|
||||
user, err := usertypes.NewRootUser(name, email, organization.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
password, err := types.NewFactorPassword(passwd, user.ID.StringValue())
|
||||
password, err := usertypes.NewFactorPassword(passwd, user.ID.StringValue())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
managedRoles := roletypes.NewManagedRoles(organization.ID)
|
||||
managedRoles := authtypes.NewManagedRoles(organization.ID)
|
||||
err = module.authz.CreateManagedUserRoleTransactions(ctx, organization.ID, user.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -726,12 +726,12 @@ func (module *Module) CreateFirstUser(ctx context.Context, organization *types.O
|
||||
|
||||
func (module *Module) Collect(ctx context.Context, orgID valuer.UUID) (map[string]any, error) {
|
||||
stats := make(map[string]any)
|
||||
counts, err := module.store.CountByOrgIDAndStatuses(ctx, orgID, []string{types.UserStatusActive.StringValue(), types.UserStatusDeleted.StringValue(), types.UserStatusPendingInvite.StringValue()})
|
||||
counts, err := module.store.CountByOrgIDAndStatuses(ctx, orgID, []string{usertypes.UserStatusActive.StringValue(), usertypes.UserStatusDeleted.StringValue(), usertypes.UserStatusPendingInvite.StringValue()})
|
||||
if err == nil {
|
||||
stats["user.count"] = counts[types.UserStatusActive] + counts[types.UserStatusDeleted] + counts[types.UserStatusPendingInvite]
|
||||
stats["user.count.active"] = counts[types.UserStatusActive]
|
||||
stats["user.count.deleted"] = counts[types.UserStatusDeleted]
|
||||
stats["user.count.pending_invite"] = counts[types.UserStatusPendingInvite]
|
||||
stats["user.count"] = counts[usertypes.UserStatusActive] + counts[usertypes.UserStatusDeleted] + counts[usertypes.UserStatusPendingInvite]
|
||||
stats["user.count.active"] = counts[usertypes.UserStatusActive]
|
||||
stats["user.count.deleted"] = counts[usertypes.UserStatusDeleted]
|
||||
stats["user.count.pending_invite"] = counts[usertypes.UserStatusPendingInvite]
|
||||
}
|
||||
|
||||
count, err := module.store.CountAPIKeyByOrgID(ctx, orgID)
|
||||
@@ -743,14 +743,14 @@ func (module *Module) Collect(ctx context.Context, orgID valuer.UUID) (map[strin
|
||||
}
|
||||
|
||||
// this function restricts that only one non-deleted user email can exist for an org ID, if found more, it throws an error
|
||||
func (module *Module) GetNonDeletedUserByEmailAndOrgID(ctx context.Context, email valuer.Email, orgID valuer.UUID) (*types.User, error) {
|
||||
func (module *Module) GetNonDeletedUserByEmailAndOrgID(ctx context.Context, email valuer.Email, orgID valuer.UUID) (*usertypes.User, error) {
|
||||
existingUsers, err := module.store.GetUsersByEmailAndOrgID(ctx, email, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// filter out the deleted users
|
||||
existingUsers = slices.DeleteFunc(existingUsers, func(user *types.User) bool { return user.ErrIfDeleted() != nil })
|
||||
existingUsers = slices.DeleteFunc(existingUsers, func(user *usertypes.User) bool { return user.ErrIfDeleted() != nil })
|
||||
|
||||
if len(existingUsers) > 1 {
|
||||
return nil, errors.Newf(errors.TypeInternal, errors.CodeInternal, "Multiple non-deleted users found for email %s in org_id: %s", email.StringValue(), orgID.StringValue())
|
||||
@@ -764,7 +764,7 @@ func (module *Module) GetNonDeletedUserByEmailAndOrgID(ctx context.Context, emai
|
||||
|
||||
}
|
||||
|
||||
func (module *Module) createUserWithoutGrant(ctx context.Context, input *types.User, opts ...root.CreateUserOption) error {
|
||||
func (module *Module) createUserWithoutGrant(ctx context.Context, input *usertypes.User, opts ...root.CreateUserOption) error {
|
||||
createUserOpts := root.NewCreateUserOptions(opts...)
|
||||
if err := module.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
if err := module.store.CreateUser(ctx, input); err != nil {
|
||||
@@ -782,25 +782,25 @@ func (module *Module) createUserWithoutGrant(ctx context.Context, input *types.U
|
||||
return err
|
||||
}
|
||||
|
||||
traitsOrProperties := types.NewTraitsFromUser(input)
|
||||
traitsOrProperties := usertypes.NewTraitsFromUser(input)
|
||||
module.analytics.IdentifyUser(ctx, input.OrgID.String(), input.ID.String(), traitsOrProperties)
|
||||
module.analytics.TrackUser(ctx, input.OrgID.String(), input.ID.String(), "User Created", traitsOrProperties)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (module *Module) activatePendingUser(ctx context.Context, user *types.User) error {
|
||||
func (module *Module) activatePendingUser(ctx context.Context, user *usertypes.User) error {
|
||||
err := module.authz.Grant(
|
||||
ctx,
|
||||
user.OrgID,
|
||||
[]string{roletypes.MustGetSigNozManagedRoleFromExistingRole(user.Role)},
|
||||
[]string{authtypes.MustGetSigNozManagedRoleFromExistingRole(user.Role)},
|
||||
authtypes.MustNewSubject(authtypes.TypeableUser, user.ID.StringValue(), user.OrgID, nil),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := user.UpdateStatus(types.UserStatusActive); err != nil {
|
||||
if err := user.UpdateStatus(usertypes.UserStatusActive); err != nil {
|
||||
return err
|
||||
}
|
||||
err = module.store.UpdateUser(ctx, user.OrgID, user)
|
||||
|
||||
@@ -11,13 +11,13 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/usertypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type service struct {
|
||||
settings factory.ScopedProviderSettings
|
||||
store types.UserStore
|
||||
store usertypes.UserStore
|
||||
module user.Module
|
||||
orgGetter organization.Getter
|
||||
authz authz.AuthZ
|
||||
@@ -27,7 +27,7 @@ type service struct {
|
||||
|
||||
func NewService(
|
||||
providerSettings factory.ProviderSettings,
|
||||
store types.UserStore,
|
||||
store usertypes.UserStore,
|
||||
module user.Module,
|
||||
orgGetter organization.Getter,
|
||||
authz authz.AuthZ,
|
||||
@@ -156,11 +156,11 @@ func (s *service) createOrPromoteRootUser(ctx context.Context, orgID valuer.UUID
|
||||
return err
|
||||
}
|
||||
|
||||
if oldRole != types.RoleAdmin {
|
||||
if oldRole != authtypes.RoleAdmin {
|
||||
if err := s.authz.ModifyGrant(ctx,
|
||||
orgID,
|
||||
[]string{roletypes.MustGetSigNozManagedRoleFromExistingRole(oldRole)},
|
||||
[]string{roletypes.MustGetSigNozManagedRoleFromExistingRole(types.RoleAdmin)},
|
||||
[]string{authtypes.MustGetSigNozManagedRoleFromExistingRole(oldRole)},
|
||||
[]string{authtypes.MustGetSigNozManagedRoleFromExistingRole(authtypes.RoleAdmin)},
|
||||
authtypes.MustNewSubject(authtypes.TypeableUser, existingUser.ID.StringValue(), orgID, nil),
|
||||
); err != nil {
|
||||
return err
|
||||
@@ -171,12 +171,12 @@ func (s *service) createOrPromoteRootUser(ctx context.Context, orgID valuer.UUID
|
||||
}
|
||||
|
||||
// Create new root user
|
||||
newUser, err := types.NewRootUser(s.config.Email.String(), s.config.Email, orgID)
|
||||
newUser, err := usertypes.NewRootUser(s.config.Email.String(), s.config.Email, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
factorPassword, err := types.NewFactorPassword(s.config.Password, newUser.ID.StringValue())
|
||||
factorPassword, err := usertypes.NewFactorPassword(s.config.Password, newUser.ID.StringValue())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -184,7 +184,7 @@ func (s *service) createOrPromoteRootUser(ctx context.Context, orgID valuer.UUID
|
||||
return s.module.CreateUser(ctx, newUser, user.WithFactorPassword(factorPassword))
|
||||
}
|
||||
|
||||
func (s *service) updateExistingRootUser(ctx context.Context, orgID valuer.UUID, existingRoot *types.User) error {
|
||||
func (s *service) updateExistingRootUser(ctx context.Context, orgID valuer.UUID, existingRoot *usertypes.User) error {
|
||||
existingRoot.PromoteToRoot()
|
||||
|
||||
if existingRoot.Email != s.config.Email {
|
||||
@@ -204,7 +204,7 @@ func (s *service) setPassword(ctx context.Context, userID valuer.UUID) error {
|
||||
return err
|
||||
}
|
||||
|
||||
factorPassword, err := types.NewFactorPassword(s.config.Password, userID.StringValue())
|
||||
factorPassword, err := usertypes.NewFactorPassword(s.config.Password, userID.StringValue())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/preferencetypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/usertypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
@@ -21,11 +21,11 @@ type store struct {
|
||||
settings factory.ProviderSettings
|
||||
}
|
||||
|
||||
func NewStore(sqlstore sqlstore.SQLStore, settings factory.ProviderSettings) types.UserStore {
|
||||
func NewStore(sqlstore sqlstore.SQLStore, settings factory.ProviderSettings) usertypes.UserStore {
|
||||
return &store{sqlstore: sqlstore, settings: settings}
|
||||
}
|
||||
|
||||
func (store *store) CreatePassword(ctx context.Context, password *types.FactorPassword) error {
|
||||
func (store *store) CreatePassword(ctx context.Context, password *usertypes.FactorPassword) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
@@ -33,13 +33,13 @@ func (store *store) CreatePassword(ctx context.Context, password *types.FactorPa
|
||||
Model(password).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return store.sqlstore.WrapAlreadyExistsErrf(err, types.ErrPasswordAlreadyExists, "password for user %s already exists", password.UserID)
|
||||
return store.sqlstore.WrapAlreadyExistsErrf(err, usertypes.ErrPasswordAlreadyExists, "password for user %s already exists", password.UserID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) CreateUser(ctx context.Context, user *types.User) error {
|
||||
func (store *store) CreateUser(ctx context.Context, user *usertypes.User) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
@@ -47,13 +47,13 @@ func (store *store) CreateUser(ctx context.Context, user *types.User) error {
|
||||
Model(user).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return store.sqlstore.WrapAlreadyExistsErrf(err, types.ErrUserAlreadyExists, "user with email %s already exists in org %s", user.Email, user.OrgID)
|
||||
return store.sqlstore.WrapAlreadyExistsErrf(err, usertypes.ErrUserAlreadyExists, "user with email %s already exists in org %s", user.Email, user.OrgID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) GetUsersByEmail(ctx context.Context, email valuer.Email) ([]*types.User, error) {
|
||||
var users []*types.User
|
||||
func (store *store) GetUsersByEmail(ctx context.Context, email valuer.Email) ([]*usertypes.User, error) {
|
||||
var users []*usertypes.User
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
@@ -69,8 +69,8 @@ func (store *store) GetUsersByEmail(ctx context.Context, email valuer.Email) ([]
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (store *store) GetUser(ctx context.Context, id valuer.UUID) (*types.User, error) {
|
||||
user := new(types.User)
|
||||
func (store *store) GetUser(ctx context.Context, id valuer.UUID) (*usertypes.User, error) {
|
||||
user := new(usertypes.User)
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
@@ -80,14 +80,14 @@ func (store *store) GetUser(ctx context.Context, id valuer.UUID) (*types.User, e
|
||||
Where("id = ?", id).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeUserNotFound, "user with id %s does not exist", id)
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrCodeUserNotFound, "user with id %s does not exist", id)
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (store *store) GetByOrgIDAndID(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*types.User, error) {
|
||||
user := new(types.User)
|
||||
func (store *store) GetByOrgIDAndID(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*usertypes.User, error) {
|
||||
user := new(usertypes.User)
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
@@ -98,14 +98,14 @@ func (store *store) GetByOrgIDAndID(ctx context.Context, orgID valuer.UUID, id v
|
||||
Where("id = ?", id).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeUserNotFound, "user with id %s does not exist", id)
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrCodeUserNotFound, "user with id %s does not exist", id)
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (store *store) GetUsersByEmailAndOrgID(ctx context.Context, email valuer.Email, orgID valuer.UUID) ([]*types.User, error) {
|
||||
var users []*types.User
|
||||
func (store *store) GetUsersByEmailAndOrgID(ctx context.Context, email valuer.Email, orgID valuer.UUID) ([]*usertypes.User, error) {
|
||||
var users []*usertypes.User
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
@@ -122,8 +122,8 @@ func (store *store) GetUsersByEmailAndOrgID(ctx context.Context, email valuer.Em
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (store *store) GetActiveUsersByRoleAndOrgID(ctx context.Context, role types.Role, orgID valuer.UUID) ([]*types.User, error) {
|
||||
var users []*types.User
|
||||
func (store *store) GetActiveUsersByRoleAndOrgID(ctx context.Context, role authtypes.LegacyRole, orgID valuer.UUID) ([]*usertypes.User, error) {
|
||||
var users []*usertypes.User
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
@@ -132,7 +132,7 @@ func (store *store) GetActiveUsersByRoleAndOrgID(ctx context.Context, role types
|
||||
Model(&users).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("role = ?", role).
|
||||
Where("status = ?", types.UserStatusActive.StringValue()).
|
||||
Where("status = ?", usertypes.UserStatusActive.StringValue()).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -141,7 +141,7 @@ func (store *store) GetActiveUsersByRoleAndOrgID(ctx context.Context, role types
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (store *store) UpdateUser(ctx context.Context, orgID valuer.UUID, user *types.User) error {
|
||||
func (store *store) UpdateUser(ctx context.Context, orgID valuer.UUID, user *usertypes.User) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
@@ -157,13 +157,13 @@ func (store *store) UpdateUser(ctx context.Context, orgID valuer.UUID, user *typ
|
||||
Where("id = ?", user.ID).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeUserNotFound, "user does not exist in org: %s", orgID)
|
||||
return store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrCodeUserNotFound, "user does not exist in org: %s", orgID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) ListUsersByOrgID(ctx context.Context, orgID valuer.UUID) ([]*types.GettableUser, error) {
|
||||
users := []*types.User{}
|
||||
func (store *store) ListUsersByOrgID(ctx context.Context, orgID valuer.UUID) ([]*usertypes.GettableUser, error) {
|
||||
users := []*usertypes.User{}
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
@@ -191,7 +191,7 @@ func (store *store) DeleteUser(ctx context.Context, orgID string, id string) err
|
||||
|
||||
// get the password id
|
||||
|
||||
var password types.FactorPassword
|
||||
var password usertypes.FactorPassword
|
||||
err = tx.NewSelect().
|
||||
Model(&password).
|
||||
Where("user_id = ?", id).
|
||||
@@ -202,7 +202,7 @@ func (store *store) DeleteUser(ctx context.Context, orgID string, id string) err
|
||||
|
||||
// delete reset password request
|
||||
_, err = tx.NewDelete().
|
||||
Model(new(types.ResetPasswordToken)).
|
||||
Model(new(usertypes.ResetPasswordToken)).
|
||||
Where("password_id = ?", password.ID.String()).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
@@ -211,7 +211,7 @@ func (store *store) DeleteUser(ctx context.Context, orgID string, id string) err
|
||||
|
||||
// delete factor password
|
||||
_, err = tx.NewDelete().
|
||||
Model(new(types.FactorPassword)).
|
||||
Model(new(usertypes.FactorPassword)).
|
||||
Where("user_id = ?", id).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
@@ -220,7 +220,7 @@ func (store *store) DeleteUser(ctx context.Context, orgID string, id string) err
|
||||
|
||||
// delete api keys
|
||||
_, err = tx.NewDelete().
|
||||
Model(&types.StorableAPIKey{}).
|
||||
Model(&usertypes.StorableAPIKey{}).
|
||||
Where("user_id = ?", id).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
@@ -247,7 +247,7 @@ func (store *store) DeleteUser(ctx context.Context, orgID string, id string) err
|
||||
|
||||
// delete user
|
||||
_, err = tx.NewDelete().
|
||||
Model(new(types.User)).
|
||||
Model(new(usertypes.User)).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("id = ?", id).
|
||||
Exec(ctx)
|
||||
@@ -275,7 +275,7 @@ func (store *store) SoftDeleteUser(ctx context.Context, orgID string, id string)
|
||||
|
||||
// get the password id
|
||||
|
||||
var password types.FactorPassword
|
||||
var password usertypes.FactorPassword
|
||||
err = tx.NewSelect().
|
||||
Model(&password).
|
||||
Where("user_id = ?", id).
|
||||
@@ -286,7 +286,7 @@ func (store *store) SoftDeleteUser(ctx context.Context, orgID string, id string)
|
||||
|
||||
// delete reset password request
|
||||
_, err = tx.NewDelete().
|
||||
Model(new(types.ResetPasswordToken)).
|
||||
Model(new(usertypes.ResetPasswordToken)).
|
||||
Where("password_id = ?", password.ID.String()).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
@@ -295,7 +295,7 @@ func (store *store) SoftDeleteUser(ctx context.Context, orgID string, id string)
|
||||
|
||||
// delete factor password
|
||||
_, err = tx.NewDelete().
|
||||
Model(new(types.FactorPassword)).
|
||||
Model(new(usertypes.FactorPassword)).
|
||||
Where("user_id = ?", id).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
@@ -304,7 +304,7 @@ func (store *store) SoftDeleteUser(ctx context.Context, orgID string, id string)
|
||||
|
||||
// delete api keys
|
||||
_, err = tx.NewDelete().
|
||||
Model(&types.StorableAPIKey{}).
|
||||
Model(&usertypes.StorableAPIKey{}).
|
||||
Where("user_id = ?", id).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
@@ -332,8 +332,8 @@ func (store *store) SoftDeleteUser(ctx context.Context, orgID string, id string)
|
||||
// soft delete user
|
||||
now := time.Now()
|
||||
_, err = tx.NewUpdate().
|
||||
Model(new(types.User)).
|
||||
Set("status = ?", types.UserStatusDeleted).
|
||||
Model(new(usertypes.User)).
|
||||
Set("status = ?", usertypes.UserStatusDeleted).
|
||||
Set("deleted_at = ?", now).
|
||||
Set("updated_at = ?", now).
|
||||
Where("org_id = ?", orgID).
|
||||
@@ -351,7 +351,7 @@ func (store *store) SoftDeleteUser(ctx context.Context, orgID string, id string)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) CreateResetPasswordToken(ctx context.Context, resetPasswordToken *types.ResetPasswordToken) error {
|
||||
func (store *store) CreateResetPasswordToken(ctx context.Context, resetPasswordToken *usertypes.ResetPasswordToken) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
@@ -359,14 +359,14 @@ func (store *store) CreateResetPasswordToken(ctx context.Context, resetPasswordT
|
||||
Model(resetPasswordToken).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return store.sqlstore.WrapAlreadyExistsErrf(err, types.ErrResetPasswordTokenAlreadyExists, "reset password token for password %s already exists", resetPasswordToken.PasswordID)
|
||||
return store.sqlstore.WrapAlreadyExistsErrf(err, usertypes.ErrResetPasswordTokenAlreadyExists, "reset password token for password %s already exists", resetPasswordToken.PasswordID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) GetPassword(ctx context.Context, id valuer.UUID) (*types.FactorPassword, error) {
|
||||
password := new(types.FactorPassword)
|
||||
func (store *store) GetPassword(ctx context.Context, id valuer.UUID) (*usertypes.FactorPassword, error) {
|
||||
password := new(usertypes.FactorPassword)
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
@@ -376,14 +376,14 @@ func (store *store) GetPassword(ctx context.Context, id valuer.UUID) (*types.Fac
|
||||
Where("id = ?", id).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrPasswordNotFound, "password with id: %s does not exist", id)
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrCodePasswordNotFound, "password with id: %s does not exist", id)
|
||||
}
|
||||
|
||||
return password, nil
|
||||
}
|
||||
|
||||
func (store *store) GetPasswordByUserID(ctx context.Context, userID valuer.UUID) (*types.FactorPassword, error) {
|
||||
password := new(types.FactorPassword)
|
||||
func (store *store) GetPasswordByUserID(ctx context.Context, userID valuer.UUID) (*usertypes.FactorPassword, error) {
|
||||
password := new(usertypes.FactorPassword)
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
@@ -393,13 +393,13 @@ func (store *store) GetPasswordByUserID(ctx context.Context, userID valuer.UUID)
|
||||
Where("user_id = ?", userID).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrPasswordNotFound, "password for user %s does not exist", userID)
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrCodePasswordNotFound, "password for user %s does not exist", userID)
|
||||
}
|
||||
return password, nil
|
||||
}
|
||||
|
||||
func (store *store) GetResetPasswordTokenByPasswordID(ctx context.Context, passwordID valuer.UUID) (*types.ResetPasswordToken, error) {
|
||||
resetPasswordToken := new(types.ResetPasswordToken)
|
||||
func (store *store) GetResetPasswordTokenByPasswordID(ctx context.Context, passwordID valuer.UUID) (*usertypes.ResetPasswordToken, error) {
|
||||
resetPasswordToken := new(usertypes.ResetPasswordToken)
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
@@ -409,7 +409,7 @@ func (store *store) GetResetPasswordTokenByPasswordID(ctx context.Context, passw
|
||||
Where("password_id = ?", passwordID).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrResetPasswordTokenNotFound, "reset password token for password %s does not exist", passwordID)
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrResetPasswordTokenNotFound, "reset password token for password %s does not exist", passwordID)
|
||||
}
|
||||
|
||||
return resetPasswordToken, nil
|
||||
@@ -417,7 +417,7 @@ func (store *store) GetResetPasswordTokenByPasswordID(ctx context.Context, passw
|
||||
|
||||
func (store *store) DeleteResetPasswordTokenByPasswordID(ctx context.Context, passwordID valuer.UUID) error {
|
||||
_, err := store.sqlstore.BunDBCtx(ctx).NewDelete().
|
||||
Model(&types.ResetPasswordToken{}).
|
||||
Model(&usertypes.ResetPasswordToken{}).
|
||||
Where("password_id = ?", passwordID).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
@@ -427,8 +427,8 @@ func (store *store) DeleteResetPasswordTokenByPasswordID(ctx context.Context, pa
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) GetResetPasswordToken(ctx context.Context, token string) (*types.ResetPasswordToken, error) {
|
||||
resetPasswordRequest := new(types.ResetPasswordToken)
|
||||
func (store *store) GetResetPasswordToken(ctx context.Context, token string) (*usertypes.ResetPasswordToken, error) {
|
||||
resetPasswordRequest := new(usertypes.ResetPasswordToken)
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
@@ -438,38 +438,38 @@ func (store *store) GetResetPasswordToken(ctx context.Context, token string) (*t
|
||||
Where("token = ?", token).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrResetPasswordTokenNotFound, "reset password token does not exist")
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrResetPasswordTokenNotFound, "reset password token does not exist")
|
||||
}
|
||||
|
||||
return resetPasswordRequest, nil
|
||||
}
|
||||
|
||||
func (store *store) UpdatePassword(ctx context.Context, factorPassword *types.FactorPassword) error {
|
||||
func (store *store) UpdatePassword(ctx context.Context, factorPassword *usertypes.FactorPassword) error {
|
||||
_, err := store.sqlstore.BunDBCtx(ctx).
|
||||
NewUpdate().
|
||||
Model(factorPassword).
|
||||
Where("user_id = ?", factorPassword.UserID).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return store.sqlstore.WrapNotFoundErrf(err, types.ErrPasswordNotFound, "password for user %s does not exist", factorPassword.UserID)
|
||||
return store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrCodePasswordNotFound, "password for user %s does not exist", factorPassword.UserID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- API KEY ---
|
||||
func (store *store) CreateAPIKey(ctx context.Context, apiKey *types.StorableAPIKey) error {
|
||||
func (store *store) CreateAPIKey(ctx context.Context, apiKey *usertypes.StorableAPIKey) error {
|
||||
_, err := store.sqlstore.BunDB().NewInsert().
|
||||
Model(apiKey).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return store.sqlstore.WrapAlreadyExistsErrf(err, types.ErrAPIKeyAlreadyExists, "API key with token: %s already exists", apiKey.Token)
|
||||
return store.sqlstore.WrapAlreadyExistsErrf(err, usertypes.ErrAPIKeyAlreadyExists, "API key with token: %s already exists", apiKey.Token)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) UpdateAPIKey(ctx context.Context, id valuer.UUID, apiKey *types.StorableAPIKey, updaterID valuer.UUID) error {
|
||||
func (store *store) UpdateAPIKey(ctx context.Context, id valuer.UUID, apiKey *usertypes.StorableAPIKey, updaterID valuer.UUID) error {
|
||||
apiKey.UpdatedBy = updaterID.String()
|
||||
apiKey.UpdatedAt = time.Now()
|
||||
_, err := store.sqlstore.BunDB().NewUpdate().
|
||||
@@ -479,13 +479,13 @@ func (store *store) UpdateAPIKey(ctx context.Context, id valuer.UUID, apiKey *ty
|
||||
Where("revoked = false").
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return store.sqlstore.WrapNotFoundErrf(err, types.ErrAPIKeyNotFound, "API key with id: %s does not exist", id)
|
||||
return store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrAPIKeyNotFound, "API key with id: %s does not exist", id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) ListAPIKeys(ctx context.Context, orgID valuer.UUID) ([]*types.StorableAPIKeyUser, error) {
|
||||
orgUserAPIKeys := new(types.OrgUserAPIKey)
|
||||
func (store *store) ListAPIKeys(ctx context.Context, orgID valuer.UUID) ([]*usertypes.StorableAPIKeyUser, error) {
|
||||
orgUserAPIKeys := new(usertypes.OrgUserAPIKey)
|
||||
|
||||
if err := store.sqlstore.BunDB().NewSelect().
|
||||
Model(orgUserAPIKeys).
|
||||
@@ -502,7 +502,7 @@ func (store *store) ListAPIKeys(ctx context.Context, orgID valuer.UUID) ([]*type
|
||||
}
|
||||
|
||||
// Flatten the API keys from all users
|
||||
var allAPIKeys []*types.StorableAPIKeyUser
|
||||
var allAPIKeys []*usertypes.StorableAPIKeyUser
|
||||
for _, user := range orgUserAPIKeys.Users {
|
||||
if user.APIKeys != nil {
|
||||
allAPIKeys = append(allAPIKeys, user.APIKeys...)
|
||||
@@ -520,7 +520,7 @@ func (store *store) ListAPIKeys(ctx context.Context, orgID valuer.UUID) ([]*type
|
||||
func (store *store) RevokeAPIKey(ctx context.Context, id, revokedByUserID valuer.UUID) error {
|
||||
updatedAt := time.Now().Unix()
|
||||
_, err := store.sqlstore.BunDB().NewUpdate().
|
||||
Model(&types.StorableAPIKey{}).
|
||||
Model(&usertypes.StorableAPIKey{}).
|
||||
Set("revoked = ?", true).
|
||||
Set("updated_by = ?", revokedByUserID).
|
||||
Set("updated_at = ?", updatedAt).
|
||||
@@ -532,8 +532,8 @@ func (store *store) RevokeAPIKey(ctx context.Context, id, revokedByUserID valuer
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) GetAPIKey(ctx context.Context, orgID, id valuer.UUID) (*types.StorableAPIKeyUser, error) {
|
||||
apiKey := new(types.OrgUserAPIKey)
|
||||
func (store *store) GetAPIKey(ctx context.Context, orgID, id valuer.UUID) (*usertypes.StorableAPIKeyUser, error) {
|
||||
apiKey := new(usertypes.OrgUserAPIKey)
|
||||
if err := store.sqlstore.BunDB().NewSelect().
|
||||
Model(apiKey).
|
||||
Relation("Users").
|
||||
@@ -545,25 +545,25 @@ func (store *store) GetAPIKey(ctx context.Context, orgID, id valuer.UUID) (*type
|
||||
Relation("Users.APIKeys.CreatedByUser").
|
||||
Relation("Users.APIKeys.UpdatedByUser").
|
||||
Scan(ctx); err != nil {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrAPIKeyNotFound, "API key with id: %s does not exist", id)
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrAPIKeyNotFound, "API key with id: %s does not exist", id)
|
||||
}
|
||||
|
||||
// flatten the API keys
|
||||
flattenedAPIKeys := []*types.StorableAPIKeyUser{}
|
||||
flattenedAPIKeys := []*usertypes.StorableAPIKeyUser{}
|
||||
for _, user := range apiKey.Users {
|
||||
if user.APIKeys != nil {
|
||||
flattenedAPIKeys = append(flattenedAPIKeys, user.APIKeys...)
|
||||
}
|
||||
}
|
||||
if len(flattenedAPIKeys) == 0 {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(errors.New(errors.TypeNotFound, errors.CodeNotFound, "API key with id: %s does not exist"), types.ErrAPIKeyNotFound, "API key with id: %s does not exist", id)
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(errors.New(errors.TypeNotFound, errors.CodeNotFound, "API key with id: %s does not exist"), usertypes.ErrAPIKeyNotFound, "API key with id: %s does not exist", id)
|
||||
}
|
||||
|
||||
return flattenedAPIKeys[0], nil
|
||||
}
|
||||
|
||||
func (store *store) CountByOrgID(ctx context.Context, orgID valuer.UUID) (int64, error) {
|
||||
user := new(types.User)
|
||||
user := new(usertypes.User)
|
||||
|
||||
count, err := store.
|
||||
sqlstore.
|
||||
@@ -580,7 +580,7 @@ func (store *store) CountByOrgID(ctx context.Context, orgID valuer.UUID) (int64,
|
||||
}
|
||||
|
||||
func (store *store) CountByOrgIDAndStatuses(ctx context.Context, orgID valuer.UUID, statuses []string) (map[valuer.String]int64, error) {
|
||||
user := new(types.User)
|
||||
user := new(usertypes.User)
|
||||
var results []struct {
|
||||
Status valuer.String `bun:"status"`
|
||||
Count int64 `bun:"count"`
|
||||
@@ -610,7 +610,7 @@ func (store *store) CountByOrgIDAndStatuses(ctx context.Context, orgID valuer.UU
|
||||
}
|
||||
|
||||
func (store *store) CountAPIKeyByOrgID(ctx context.Context, orgID valuer.UUID) (int64, error) {
|
||||
apiKey := new(types.StorableAPIKey)
|
||||
apiKey := new(usertypes.StorableAPIKey)
|
||||
|
||||
count, err := store.
|
||||
sqlstore.
|
||||
@@ -633,8 +633,8 @@ func (store *store) RunInTx(ctx context.Context, cb func(ctx context.Context) er
|
||||
})
|
||||
}
|
||||
|
||||
func (store *store) GetRootUserByOrgID(ctx context.Context, orgID valuer.UUID) (*types.User, error) {
|
||||
user := new(types.User)
|
||||
func (store *store) GetRootUserByOrgID(ctx context.Context, orgID valuer.UUID) (*usertypes.User, error) {
|
||||
user := new(usertypes.User)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
@@ -644,13 +644,13 @@ func (store *store) GetRootUserByOrgID(ctx context.Context, orgID valuer.UUID) (
|
||||
Where("is_root = ?", true).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeUserNotFound, "root user for org %s not found", orgID)
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrCodeUserNotFound, "root user for org %s not found", orgID)
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (store *store) ListUsersByEmailAndOrgIDs(ctx context.Context, email valuer.Email, orgIDs []valuer.UUID) ([]*types.User, error) {
|
||||
users := []*types.User{}
|
||||
func (store *store) ListUsersByEmailAndOrgIDs(ctx context.Context, email valuer.Email, orgIDs []valuer.UUID) ([]*usertypes.User, error) {
|
||||
users := []*usertypes.User{}
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
@@ -666,8 +666,8 @@ func (store *store) ListUsersByEmailAndOrgIDs(ctx context.Context, email valuer.
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (store *store) GetUserByResetPasswordToken(ctx context.Context, token string) (*types.User, error) {
|
||||
user := new(types.User)
|
||||
func (store *store) GetUserByResetPasswordToken(ctx context.Context, token string) (*usertypes.User, error) {
|
||||
user := new(usertypes.User)
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
@@ -679,14 +679,14 @@ func (store *store) GetUserByResetPasswordToken(ctx context.Context, token strin
|
||||
Where("reset_password_token.token = ?", token).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeUserNotFound, "user not found for reset password token")
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrCodeUserNotFound, "user not found for reset password token")
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (store *store) GetUsersByEmailsOrgIDAndStatuses(ctx context.Context, orgID valuer.UUID, emails []string, statuses []string) ([]*types.User, error) {
|
||||
users := []*types.User{}
|
||||
func (store *store) GetUsersByEmailsOrgIDAndStatuses(ctx context.Context, orgID valuer.UUID, emails []string, statuses []string) ([]*usertypes.User, error) {
|
||||
users := []*usertypes.User{}
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
@@ -703,3 +703,34 @@ func (store *store) GetUsersByEmailsOrgIDAndStatuses(ctx context.Context, orgID
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (store *store) GetActiveUserAndFactorPasswordByEmailAndOrgID(ctx context.Context, email string, orgID valuer.UUID) (*usertypes.User, *usertypes.FactorPassword, error) {
|
||||
user := new(usertypes.User)
|
||||
factorPassword := new(usertypes.FactorPassword)
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewSelect().
|
||||
Model(user).
|
||||
Where("email = ?", email).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("status = ?", usertypes.UserStatusActive.StringValue()).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrCodeUserNotFound, "user with email %s in org %s not found", email, orgID)
|
||||
}
|
||||
|
||||
err = store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewSelect().
|
||||
Model(factorPassword).
|
||||
Where("user_id = ?", user.ID).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrCodePasswordNotFound, "user with email %s in org %s does not have password", email, orgID)
|
||||
}
|
||||
|
||||
return user, factorPassword, nil
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/usertypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type createUserOptions struct {
|
||||
FactorPassword *types.FactorPassword
|
||||
FactorPassword *usertypes.FactorPassword
|
||||
}
|
||||
|
||||
type CreateUserOption func(*createUserOptions)
|
||||
|
||||
func WithFactorPassword(factorPassword *types.FactorPassword) CreateUserOption {
|
||||
func WithFactorPassword(factorPassword *usertypes.FactorPassword) CreateUserOption {
|
||||
return func(o *createUserOptions) {
|
||||
o.FactorPassword = factorPassword
|
||||
}
|
||||
|
||||
@@ -6,22 +6,23 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/statsreporter"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/usertypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type Module interface {
|
||||
// Creates the organization and the first user of that organization.
|
||||
CreateFirstUser(ctx context.Context, organization *types.Organization, name string, email valuer.Email, password string) (*types.User, error)
|
||||
CreateFirstUser(ctx context.Context, organization *types.Organization, name string, email valuer.Email, password string) (*usertypes.User, error)
|
||||
|
||||
// Creates a user and sends an analytics event.
|
||||
CreateUser(ctx context.Context, user *types.User, opts ...CreateUserOption) error
|
||||
CreateUser(ctx context.Context, user *usertypes.User, opts ...CreateUserOption) error
|
||||
|
||||
// Get or create a user. If a user with the same email and orgID already exists, it returns the existing user.
|
||||
GetOrCreateUser(ctx context.Context, user *types.User, opts ...CreateUserOption) (*types.User, error)
|
||||
GetOrCreateUser(ctx context.Context, user *usertypes.User, opts ...CreateUserOption) (*usertypes.User, error)
|
||||
|
||||
// Get or Create a reset password token for a user. If the password does not exist, a new one is randomly generated and inserted. The function
|
||||
// is idempotent and can be called multiple times.
|
||||
GetOrCreateResetPasswordToken(ctx context.Context, userID valuer.UUID) (*types.ResetPasswordToken, error)
|
||||
GetOrCreateResetPasswordToken(ctx context.Context, userID valuer.UUID) (*usertypes.ResetPasswordToken, error)
|
||||
|
||||
// Updates password of a user using a reset password token. It also deletes all reset password tokens for the user.
|
||||
// This is used to reset the password of a user when they forget their password.
|
||||
@@ -33,48 +34,48 @@ type Module interface {
|
||||
// Initiate forgot password flow for a user
|
||||
ForgotPassword(ctx context.Context, orgID valuer.UUID, email valuer.Email, frontendBaseURL string) error
|
||||
|
||||
UpdateUser(ctx context.Context, orgID valuer.UUID, id string, user *types.User, updatedBy string) (*types.User, error)
|
||||
UpdateUser(ctx context.Context, orgID valuer.UUID, id string, user *usertypes.User, updatedBy string) (*usertypes.User, error)
|
||||
|
||||
// UpdateAnyUser updates a user and persists the changes to the database along with the analytics and identity deletion.
|
||||
UpdateAnyUser(ctx context.Context, orgID valuer.UUID, user *types.User) error
|
||||
UpdateAnyUser(ctx context.Context, orgID valuer.UUID, user *usertypes.User) error
|
||||
DeleteUser(ctx context.Context, orgID valuer.UUID, id string, deletedBy string) error
|
||||
|
||||
// invite
|
||||
CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID valuer.UUID, bulkInvites *types.PostableBulkInviteRequest) ([]*types.Invite, error)
|
||||
ListInvite(ctx context.Context, orgID string) ([]*types.Invite, error)
|
||||
AcceptInvite(ctx context.Context, token string, password string) (*types.User, error)
|
||||
GetInviteByToken(ctx context.Context, token string) (*types.Invite, error)
|
||||
CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID valuer.UUID, bulkInvites *usertypes.PostableBulkInviteRequest) ([]*usertypes.Invite, error)
|
||||
ListInvite(ctx context.Context, orgID string) ([]*usertypes.Invite, error)
|
||||
AcceptInvite(ctx context.Context, token string, password string) (*usertypes.User, error)
|
||||
GetInviteByToken(ctx context.Context, token string) (*usertypes.Invite, error)
|
||||
|
||||
// API KEY
|
||||
CreateAPIKey(ctx context.Context, apiKey *types.StorableAPIKey) error
|
||||
UpdateAPIKey(ctx context.Context, id valuer.UUID, apiKey *types.StorableAPIKey, updaterID valuer.UUID) error
|
||||
ListAPIKeys(ctx context.Context, orgID valuer.UUID) ([]*types.StorableAPIKeyUser, error)
|
||||
CreateAPIKey(ctx context.Context, apiKey *usertypes.StorableAPIKey) error
|
||||
UpdateAPIKey(ctx context.Context, id valuer.UUID, apiKey *usertypes.StorableAPIKey, updaterID valuer.UUID) error
|
||||
ListAPIKeys(ctx context.Context, orgID valuer.UUID) ([]*usertypes.StorableAPIKeyUser, error)
|
||||
RevokeAPIKey(ctx context.Context, id, removedByUserID valuer.UUID) error
|
||||
GetAPIKey(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*types.StorableAPIKeyUser, error)
|
||||
GetAPIKey(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*usertypes.StorableAPIKeyUser, error)
|
||||
|
||||
GetNonDeletedUserByEmailAndOrgID(ctx context.Context, email valuer.Email, orgID valuer.UUID) (*types.User, error)
|
||||
GetNonDeletedUserByEmailAndOrgID(ctx context.Context, email valuer.Email, orgID valuer.UUID) (*usertypes.User, error)
|
||||
|
||||
statsreporter.StatsCollector
|
||||
}
|
||||
|
||||
type Getter interface {
|
||||
// Get root user by org id.
|
||||
GetRootUserByOrgID(context.Context, valuer.UUID) (*types.User, error)
|
||||
GetRootUserByOrgID(context.Context, valuer.UUID) (*usertypes.User, error)
|
||||
|
||||
// Get gets the users based on the given id
|
||||
ListByOrgID(context.Context, valuer.UUID) ([]*types.User, error)
|
||||
ListByOrgID(context.Context, valuer.UUID) ([]*usertypes.User, error)
|
||||
|
||||
// Get users by email.
|
||||
GetUsersByEmail(context.Context, valuer.Email) ([]*types.User, error)
|
||||
GetUsersByEmail(context.Context, valuer.Email) ([]*usertypes.User, error)
|
||||
|
||||
// Get user by orgID and id.
|
||||
GetByOrgIDAndID(context.Context, valuer.UUID, valuer.UUID) (*types.User, error)
|
||||
GetByOrgIDAndID(context.Context, valuer.UUID, valuer.UUID) (*usertypes.User, error)
|
||||
|
||||
// Get user by id.
|
||||
Get(context.Context, valuer.UUID) (*types.User, error)
|
||||
Get(context.Context, valuer.UUID) (*usertypes.User, error)
|
||||
|
||||
// List users by email and org ids.
|
||||
ListUsersByEmailAndOrgIDs(context.Context, valuer.Email, []valuer.UUID) ([]*types.User, error)
|
||||
ListUsersByEmailAndOrgIDs(context.Context, valuer.Email, []valuer.UUID) ([]*usertypes.User, error)
|
||||
|
||||
// Count users by org id.
|
||||
CountByOrgID(context.Context, valuer.UUID) (int64, error)
|
||||
@@ -83,7 +84,10 @@ type Getter interface {
|
||||
CountByOrgIDAndStatuses(context.Context, valuer.UUID, []string) (map[valuer.String]int64, error)
|
||||
|
||||
// Get factor password by user id.
|
||||
GetFactorPasswordByUserID(context.Context, valuer.UUID) (*types.FactorPassword, error)
|
||||
GetFactorPasswordByUserID(context.Context, valuer.UUID) (*usertypes.FactorPassword, error)
|
||||
|
||||
// Get Active User and FactorPassword by email and org id.
|
||||
GetActiveUserAndFactorPasswordByEmailAndOrgID(ctx context.Context, email string, orgID valuer.UUID) (*usertypes.User, *usertypes.FactorPassword, error)
|
||||
}
|
||||
|
||||
type Handler interface {
|
||||
|
||||
@@ -73,6 +73,7 @@ import (
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/ruletypes"
|
||||
traceFunnels "github.com/SigNoz/signoz/pkg/types/tracefunneltypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/usertypes"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/integrations/messagingQueues/kafka"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/logparsingpipeline"
|
||||
@@ -2034,7 +2035,7 @@ func (aH *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
var req types.PostableRegisterOrgAndAdmin
|
||||
var req usertypes.PostableRegisterOrgAndAdmin
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
|
||||
@@ -8,11 +8,12 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/authn/passwordauthn/emailpasswordauthn"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
)
|
||||
|
||||
func NewAuthNs(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing) (map[authtypes.AuthNProvider]authn.AuthN, error) {
|
||||
emailPasswordAuthN := emailpasswordauthn.New(store)
|
||||
func NewAuthNs(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing, userGetter user.Getter) (map[authtypes.AuthNProvider]authn.AuthN, error) {
|
||||
emailPasswordAuthN := emailpasswordauthn.New(userGetter)
|
||||
|
||||
googleCallbackAuthN, err := googlecallbackauthn.New(ctx, store, providerSettings)
|
||||
if err != nil {
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/flagger"
|
||||
"github.com/SigNoz/signoz/pkg/gateway"
|
||||
"github.com/SigNoz/signoz/pkg/global"
|
||||
"github.com/SigNoz/signoz/pkg/identn"
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation"
|
||||
"github.com/SigNoz/signoz/pkg/modules/metricsexplorer"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
@@ -114,9 +113,6 @@ type Config struct {
|
||||
|
||||
// User config
|
||||
User user.Config `mapstructure:"user"`
|
||||
|
||||
// IdentN config
|
||||
IdentN identn.Config `mapstructure:"identn"`
|
||||
}
|
||||
|
||||
// DeprecatedFlags are the flags that are deprecated and scheduled for removal.
|
||||
@@ -180,7 +176,6 @@ func NewConfig(ctx context.Context, logger *slog.Logger, resolverConfig config.R
|
||||
metricsexplorer.NewConfigFactory(),
|
||||
flagger.NewConfigFactory(),
|
||||
user.NewConfigFactory(),
|
||||
identn.NewConfigFactory(),
|
||||
}
|
||||
|
||||
conf, err := config.New(ctx, resolverConfig, configFactories)
|
||||
|
||||
@@ -22,9 +22,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/flagger/configflagger"
|
||||
"github.com/SigNoz/signoz/pkg/global"
|
||||
"github.com/SigNoz/signoz/pkg/global/signozglobal"
|
||||
"github.com/SigNoz/signoz/pkg/identn"
|
||||
"github.com/SigNoz/signoz/pkg/identn/apikeyidentn"
|
||||
"github.com/SigNoz/signoz/pkg/identn/tokenizeridentn"
|
||||
"github.com/SigNoz/signoz/pkg/modules/authdomain/implauthdomain"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
|
||||
@@ -274,13 +271,6 @@ func NewTokenizerProviderFactories(cache cache.Cache, sqlstore sqlstore.SQLStore
|
||||
)
|
||||
}
|
||||
|
||||
func NewIdentNProviderFactories(sqlstore sqlstore.SQLStore, tokenizer tokenizer.Tokenizer) factory.NamedMap[factory.ProviderFactory[identn.IdentN, identn.Config]] {
|
||||
return factory.MustNewNamedMap(
|
||||
tokenizeridentn.NewFactory(tokenizer),
|
||||
apikeyidentn.NewFactory(sqlstore),
|
||||
)
|
||||
}
|
||||
|
||||
func NewGlobalProviderFactories() factory.NamedMap[factory.ProviderFactory[global.Global, global.Config]] {
|
||||
return factory.MustNewNamedMap(
|
||||
signozglobal.NewFactory(),
|
||||
|
||||
@@ -17,11 +17,14 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/flagger"
|
||||
"github.com/SigNoz/signoz/pkg/gateway"
|
||||
"github.com/SigNoz/signoz/pkg/identn"
|
||||
"github.com/SigNoz/signoz/pkg/identn/apikeyidentn"
|
||||
"github.com/SigNoz/signoz/pkg/identn/tokenizeridentn"
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user/impluser"
|
||||
"github.com/SigNoz/signoz/pkg/prometheus"
|
||||
"github.com/SigNoz/signoz/pkg/querier"
|
||||
@@ -88,7 +91,7 @@ func New(
|
||||
sqlSchemaProviderFactories func(sqlstore.SQLStore) factory.NamedMap[factory.ProviderFactory[sqlschema.SQLSchema, sqlschema.Config]],
|
||||
sqlstoreProviderFactories factory.NamedMap[factory.ProviderFactory[sqlstore.SQLStore, sqlstore.Config]],
|
||||
telemetrystoreProviderFactories factory.NamedMap[factory.ProviderFactory[telemetrystore.TelemetryStore, telemetrystore.Config]],
|
||||
authNsCallback func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing) (map[authtypes.AuthNProvider]authn.AuthN, error),
|
||||
authNsCallback func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing, userGetter user.Getter) (map[authtypes.AuthNProvider]authn.AuthN, error),
|
||||
authzCallback func(context.Context, sqlstore.SQLStore, licensing.Licensing, dashboard.Module) factory.ProviderFactory[authz.AuthZ, authz.Config],
|
||||
dashboardModuleCallback func(sqlstore.SQLStore, factory.ProviderSettings, analytics.Analytics, organization.Getter, queryparser.QueryParser, querier.Querier, licensing.Licensing) dashboard.Module,
|
||||
gatewayProviderFactory func(licensing.Licensing) factory.ProviderFactory[gateway.Gateway, gateway.Config],
|
||||
@@ -351,7 +354,7 @@ func New(
|
||||
|
||||
// Initialize authns
|
||||
store := sqlauthnstore.NewStore(sqlstore)
|
||||
authNs, err := authNsCallback(ctx, providerSettings, store, licensing)
|
||||
authNs, err := authNsCallback(ctx, providerSettings, store, licensing, userGetter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -393,16 +396,9 @@ func New(
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, analytics, querier, telemetrystore, telemetryMetadataStore, authNs, authz, cache, queryParser, config, dashboard, userGetter)
|
||||
|
||||
// Initialize identN resolver
|
||||
identNFactories := NewIdentNProviderFactories(sqlstore, tokenizer)
|
||||
identNs := []identn.IdentN{}
|
||||
for _, identNFactory := range identNFactories.GetInOrder() {
|
||||
identN, err := identNFactory.New(ctx, providerSettings, config.IdentN)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
identNs = append(identNs, identN)
|
||||
}
|
||||
identNResolver := identn.NewIdentNResolver(providerSettings, identNs...)
|
||||
tokenizeridentN := tokenizeridentn.New(providerSettings, tokenizer, []string{"Authorization", "Sec-WebSocket-Protocol"})
|
||||
apikeyIdentN := apikeyidentn.New(providerSettings, sqlstore, []string{"SIGNOZ-API-KEY"})
|
||||
identNResolver := identn.NewIdentNResolver(providerSettings, tokenizeridentN, apikeyIdentN)
|
||||
|
||||
userService := impluser.NewService(providerSettings, impluser.NewStore(sqlstore, providerSettings), modules.User, orgGetter, authz, config.User.Root)
|
||||
|
||||
|
||||
@@ -2,9 +2,11 @@ package sqlmigration
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/usertypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/migrate"
|
||||
@@ -16,12 +18,12 @@ type funnel struct {
|
||||
types.Identifiable // funnel id
|
||||
types.TimeAuditable
|
||||
types.UserAuditable
|
||||
Name string `json:"funnel_name" bun:"name,type:text,notnull"` // funnel name
|
||||
Description string `json:"description" bun:"description,type:text"` // funnel description
|
||||
OrgID valuer.UUID `json:"org_id" bun:"org_id,type:varchar,notnull"`
|
||||
Steps []funnelStep `json:"steps" bun:"steps,type:text,notnull"`
|
||||
Tags string `json:"tags" bun:"tags,type:text"`
|
||||
CreatedByUser *types.User `json:"user" bun:"rel:belongs-to,join:created_by=id"`
|
||||
Name string `json:"funnel_name" bun:"name,type:text,notnull"` // funnel name
|
||||
Description string `json:"description" bun:"description,type:text"` // funnel description
|
||||
OrgID valuer.UUID `json:"org_id" bun:"org_id,type:varchar,notnull"`
|
||||
Steps []funnelStep `json:"steps" bun:"steps,type:text,notnull"`
|
||||
Tags string `json:"tags" bun:"tags,type:text"`
|
||||
CreatedByUser *usertypes.User `json:"user" bun:"rel:belongs-to,join:created_by=id"`
|
||||
}
|
||||
|
||||
type funnelStep struct {
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/sqlschema"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/migrate"
|
||||
@@ -54,7 +54,7 @@ func (migration *addManagedRoles) Up(ctx context.Context, db *bun.DB) error {
|
||||
return err
|
||||
}
|
||||
|
||||
managedRoles := []*roletypes.StorableRole{}
|
||||
managedRoles := []*authtypes.StorableRole{}
|
||||
for _, orgIDStr := range orgIDs {
|
||||
orgID, err := valuer.NewUUID(orgIDStr)
|
||||
if err != nil {
|
||||
@@ -62,20 +62,20 @@ func (migration *addManagedRoles) Up(ctx context.Context, db *bun.DB) error {
|
||||
}
|
||||
|
||||
// signoz admin
|
||||
signozAdminRole := roletypes.NewRole(roletypes.SigNozAdminRoleName, roletypes.SigNozAdminRoleDescription, roletypes.RoleTypeManaged, orgID)
|
||||
managedRoles = append(managedRoles, roletypes.NewStorableRoleFromRole(signozAdminRole))
|
||||
signozAdminRole := authtypes.NewRole(authtypes.SigNozAdminRoleName, authtypes.SigNozAdminRoleDescription, authtypes.RoleTypeManaged, orgID)
|
||||
managedRoles = append(managedRoles, authtypes.NewStorableRoleFromRole(signozAdminRole))
|
||||
|
||||
// signoz editor
|
||||
signozEditorRole := roletypes.NewRole(roletypes.SigNozEditorRoleName, roletypes.SigNozEditorRoleDescription, roletypes.RoleTypeManaged, orgID)
|
||||
managedRoles = append(managedRoles, roletypes.NewStorableRoleFromRole(signozEditorRole))
|
||||
signozEditorRole := authtypes.NewRole(authtypes.SigNozEditorRoleName, authtypes.SigNozEditorRoleDescription, authtypes.RoleTypeManaged, orgID)
|
||||
managedRoles = append(managedRoles, authtypes.NewStorableRoleFromRole(signozEditorRole))
|
||||
|
||||
// signoz viewer
|
||||
signozViewerRole := roletypes.NewRole(roletypes.SigNozViewerRoleName, roletypes.SigNozViewerRoleDescription, roletypes.RoleTypeManaged, orgID)
|
||||
managedRoles = append(managedRoles, roletypes.NewStorableRoleFromRole(signozViewerRole))
|
||||
signozViewerRole := authtypes.NewRole(authtypes.SigNozViewerRoleName, authtypes.SigNozViewerRoleDescription, authtypes.RoleTypeManaged, orgID)
|
||||
managedRoles = append(managedRoles, authtypes.NewStorableRoleFromRole(signozViewerRole))
|
||||
|
||||
// signoz anonymous
|
||||
signozAnonymousRole := roletypes.NewRole(roletypes.SigNozAnonymousRoleName, roletypes.SigNozAnonymousRoleDescription, roletypes.RoleTypeManaged, orgID)
|
||||
managedRoles = append(managedRoles, roletypes.NewStorableRoleFromRole(signozAnonymousRole))
|
||||
signozAnonymousRole := authtypes.NewRole(authtypes.SigNozAnonymousRoleName, authtypes.SigNozAnonymousRoleDescription, authtypes.RoleTypeManaged, orgID)
|
||||
managedRoles = append(managedRoles, authtypes.NewStorableRoleFromRole(signozAnonymousRole))
|
||||
}
|
||||
|
||||
if len(managedRoles) > 0 {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/oklog/ulid/v2"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/dialect"
|
||||
@@ -83,7 +83,7 @@ func (migration *addAnonymousPublicDashboardTransaction) Up(ctx context.Context,
|
||||
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, "metaresource", "organization/"+orgID+"/public-dashboard/*", "read", "role:organization/"+orgID+"/role/"+roletypes.SigNozAnonymousRoleName+"#assignee", "userset", tupleID, now,
|
||||
storeID, "metaresource", "organization/"+orgID+"/public-dashboard/*", "read", "role:organization/"+orgID+"/role/"+authtypes.SigNozAnonymousRoleName+"#assignee", "userset", tupleID, now,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -102,7 +102,7 @@ func (migration *addAnonymousPublicDashboardTransaction) Up(ctx context.Context,
|
||||
INSERT INTO changelog (store, object_type, object_id, relation, _user, operation, ulid, inserted_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (store, ulid, object_type) DO NOTHING`,
|
||||
storeID, "metaresource", "organization/"+orgID+"/public-dashboard/*", "read", "role:organization/"+orgID+"/role/"+roletypes.SigNozAnonymousRoleName+"#assignee", "TUPLE_OPERATION_WRITE", tupleID, now,
|
||||
storeID, "metaresource", "organization/"+orgID+"/public-dashboard/*", "read", "role:organization/"+orgID+"/role/"+authtypes.SigNozAnonymousRoleName+"#assignee", "TUPLE_OPERATION_WRITE", tupleID, now,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -113,7 +113,7 @@ func (migration *addAnonymousPublicDashboardTransaction) Up(ctx context.Context,
|
||||
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, "metaresource", "organization/"+orgID+"/public-dashboard/*", "read", "role", "organization/"+orgID+"/role/"+roletypes.SigNozAnonymousRoleName, "assignee", "userset", tupleID, now,
|
||||
storeID, "metaresource", "organization/"+orgID+"/public-dashboard/*", "read", "role", "organization/"+orgID+"/role/"+authtypes.SigNozAnonymousRoleName, "assignee", "userset", tupleID, now,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -132,7 +132,7 @@ func (migration *addAnonymousPublicDashboardTransaction) Up(ctx context.Context,
|
||||
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, "metaresource", "organization/"+orgID+"/public-dashboard/*", "read", "role", "organization/"+orgID+"/role/"+roletypes.SigNozAnonymousRoleName, "assignee", 0, tupleID, now,
|
||||
storeID, "metaresource", "organization/"+orgID+"/public-dashboard/*", "read", "role", "organization/"+orgID+"/role/"+authtypes.SigNozAnonymousRoleName, "assignee", 0, tupleID, now,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -8,9 +8,8 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
IndexTypeUnique = IndexType{s: valuer.NewString("uq")}
|
||||
IndexTypeIndex = IndexType{s: valuer.NewString("ix")}
|
||||
IndexTypePartialUnique = IndexType{s: valuer.NewString("puq")}
|
||||
IndexTypeUnique = IndexType{s: valuer.NewString("uq")}
|
||||
IndexTypeIndex = IndexType{s: valuer.NewString("ix")}
|
||||
)
|
||||
|
||||
type IndexType struct{ s valuer.String }
|
||||
@@ -23,7 +22,6 @@ type Index interface {
|
||||
// The name of the index.
|
||||
// - Indexes are named as `ix_<table_name>_<column_names>`. The column names are separated by underscores.
|
||||
// - Unique constraints are named as `uq_<table_name>_<column_names>`. The column names are separated by underscores.
|
||||
// - Partial unique indexes are named as `puq_<table_name>_<column_names>_<predicate_hash>`.
|
||||
// The name is autogenerated and should not be set by the user.
|
||||
Name() string
|
||||
|
||||
@@ -135,101 +133,3 @@ func (index *UniqueIndex) ToDropSQL(fmter SQLFormatter) []byte {
|
||||
|
||||
return sql
|
||||
}
|
||||
|
||||
type PartialUniqueIndex struct {
|
||||
TableName TableName
|
||||
ColumnNames []ColumnName
|
||||
Where string
|
||||
name string
|
||||
}
|
||||
|
||||
func (index *PartialUniqueIndex) Name() string {
|
||||
if index.name != "" {
|
||||
return index.name
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
b.WriteString(IndexTypePartialUnique.String())
|
||||
b.WriteString("_")
|
||||
b.WriteString(string(index.TableName))
|
||||
b.WriteString("_")
|
||||
for i, column := range index.ColumnNames {
|
||||
if i > 0 {
|
||||
b.WriteString("_")
|
||||
}
|
||||
b.WriteString(string(column))
|
||||
}
|
||||
b.WriteString("_")
|
||||
b.WriteString((&whereNormalizer{input: index.Where}).hash())
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (index *PartialUniqueIndex) Named(name string) Index {
|
||||
copyOfColumnNames := make([]ColumnName, len(index.ColumnNames))
|
||||
copy(copyOfColumnNames, index.ColumnNames)
|
||||
|
||||
return &PartialUniqueIndex{
|
||||
TableName: index.TableName,
|
||||
ColumnNames: copyOfColumnNames,
|
||||
Where: index.Where,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (index *PartialUniqueIndex) IsNamed() bool {
|
||||
return index.name != ""
|
||||
}
|
||||
|
||||
func (*PartialUniqueIndex) Type() IndexType {
|
||||
return IndexTypePartialUnique
|
||||
}
|
||||
|
||||
func (index *PartialUniqueIndex) Columns() []ColumnName {
|
||||
return index.ColumnNames
|
||||
}
|
||||
|
||||
func (index *PartialUniqueIndex) Equals(other Index) bool {
|
||||
if other.Type() != IndexTypePartialUnique {
|
||||
return false
|
||||
}
|
||||
|
||||
otherPartial, ok := other.(*PartialUniqueIndex)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return index.Name() == other.Name() && slices.Equal(index.Columns(), other.Columns()) && (&whereNormalizer{input: index.Where}).normalize() == (&whereNormalizer{input: otherPartial.Where}).normalize()
|
||||
}
|
||||
|
||||
func (index *PartialUniqueIndex) ToCreateSQL(fmter SQLFormatter) []byte {
|
||||
sql := []byte{}
|
||||
|
||||
sql = append(sql, "CREATE UNIQUE INDEX IF NOT EXISTS "...)
|
||||
sql = fmter.AppendIdent(sql, index.Name())
|
||||
sql = append(sql, " ON "...)
|
||||
sql = fmter.AppendIdent(sql, string(index.TableName))
|
||||
sql = append(sql, " ("...)
|
||||
|
||||
for i, column := range index.ColumnNames {
|
||||
if i > 0 {
|
||||
sql = append(sql, ", "...)
|
||||
}
|
||||
|
||||
sql = fmter.AppendIdent(sql, string(column))
|
||||
}
|
||||
|
||||
sql = append(sql, ") WHERE "...)
|
||||
sql = append(sql, index.Where...)
|
||||
|
||||
return sql
|
||||
}
|
||||
|
||||
func (index *PartialUniqueIndex) ToDropSQL(fmter SQLFormatter) []byte {
|
||||
sql := []byte{}
|
||||
|
||||
sql = append(sql, "DROP INDEX IF EXISTS "...)
|
||||
sql = fmter.AppendIdent(sql, index.Name())
|
||||
|
||||
return sql
|
||||
}
|
||||
|
||||
|
||||
@@ -38,110 +38,6 @@ func TestIndexToCreateSQL(t *testing.T) {
|
||||
},
|
||||
sql: `CREATE UNIQUE INDEX IF NOT EXISTS "my_index" ON "users" ("id", "name", "email")`,
|
||||
},
|
||||
{
|
||||
name: "PartialUnique_1Column",
|
||||
index: &PartialUniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
Where: `"deleted_at" IS NULL`,
|
||||
},
|
||||
sql: `CREATE UNIQUE INDEX IF NOT EXISTS "puq_users_email_94610c77" ON "users" ("email") WHERE "deleted_at" IS NULL`,
|
||||
},
|
||||
{
|
||||
name: "PartialUnique_2Columns",
|
||||
index: &PartialUniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"org_id", "email"},
|
||||
Where: `"deleted_at" IS NULL`,
|
||||
},
|
||||
sql: `CREATE UNIQUE INDEX IF NOT EXISTS "puq_users_org_id_email_94610c77" ON "users" ("org_id", "email") WHERE "deleted_at" IS NULL`,
|
||||
},
|
||||
{
|
||||
name: "PartialUnique_Named",
|
||||
index: &PartialUniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
Where: `"deleted_at" IS NULL`,
|
||||
name: "my_partial_index",
|
||||
},
|
||||
sql: `CREATE UNIQUE INDEX IF NOT EXISTS "my_partial_index" ON "users" ("email") WHERE "deleted_at" IS NULL`,
|
||||
},
|
||||
{
|
||||
name: "PartialUnique_WhereWithParentheses",
|
||||
index: &PartialUniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
Where: `("deleted_at" IS NULL)`,
|
||||
},
|
||||
sql: `CREATE UNIQUE INDEX IF NOT EXISTS "puq_users_email_94610c77" ON "users" ("email") WHERE ("deleted_at" IS NULL)`,
|
||||
},
|
||||
{
|
||||
name: "PartialUnique_WhereWithQuotedIdentifier",
|
||||
index: &PartialUniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
Where: `"order" IS NULL`,
|
||||
},
|
||||
sql: `CREATE UNIQUE INDEX IF NOT EXISTS "puq_users_email_14c5f5f2" ON "users" ("email") WHERE "order" IS NULL`,
|
||||
},
|
||||
{
|
||||
name: "PartialUnique_WhereWithQuotedLiteral",
|
||||
index: &PartialUniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
Where: `status = 'somewhere'`,
|
||||
},
|
||||
sql: `CREATE UNIQUE INDEX IF NOT EXISTS "puq_users_email_9817c709" ON "users" ("email") WHERE status = 'somewhere'`,
|
||||
},
|
||||
{
|
||||
name: "PartialUnique_WhereWith2Columns",
|
||||
index: &PartialUniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email", "status"},
|
||||
Where: `email = 'test@example.com' AND status = 'active'`,
|
||||
},
|
||||
sql: `CREATE UNIQUE INDEX IF NOT EXISTS "puq_users_email_status_e70e78c3" ON "users" ("email", "status") WHERE email = 'test@example.com' AND status = 'active'`,
|
||||
},
|
||||
// postgres docs example
|
||||
{
|
||||
name: "PartialUnique_WhereWithPostgresDocsExample_1",
|
||||
index: &PartialUniqueIndex{
|
||||
TableName: "access_log",
|
||||
ColumnNames: []ColumnName{"client_ip"},
|
||||
Where: `NOT (client_ip > inet '192.168.100.0' AND client_ip < inet '192.168.100.255')`,
|
||||
},
|
||||
sql: `CREATE UNIQUE INDEX IF NOT EXISTS "puq_access_log_client_ip_5a596410" ON "access_log" ("client_ip") WHERE NOT (client_ip > inet '192.168.100.0' AND client_ip < inet '192.168.100.255')`,
|
||||
},
|
||||
// postgres docs example
|
||||
{
|
||||
name: "PartialUnique_WhereWithPostgresDocsExample_2",
|
||||
index: &PartialUniqueIndex{
|
||||
TableName: "orders",
|
||||
ColumnNames: []ColumnName{"order_nr"},
|
||||
Where: `billed is not true`,
|
||||
},
|
||||
sql: `CREATE UNIQUE INDEX IF NOT EXISTS "puq_orders_order_nr_6d31bb0e" ON "orders" ("order_nr") WHERE billed is not true`,
|
||||
},
|
||||
// sqlite docs example
|
||||
{
|
||||
name: "PartialUnique_WhereWithSqliteDocsExample_1",
|
||||
index: &PartialUniqueIndex{
|
||||
TableName: "person",
|
||||
ColumnNames: []ColumnName{"team_id"},
|
||||
Where: `is_team_leader`,
|
||||
},
|
||||
sql: `CREATE UNIQUE INDEX IF NOT EXISTS "puq_person_team_id_c8604a29" ON "person" ("team_id") WHERE is_team_leader`,
|
||||
},
|
||||
// sqlite docs example
|
||||
{
|
||||
name: "PartialUnique_WhereWithSqliteDocsExample_2",
|
||||
index: &PartialUniqueIndex{
|
||||
TableName: "purchaseorder",
|
||||
ColumnNames: []ColumnName{"parent_po"},
|
||||
Where: `parent_po IS NOT NULL`,
|
||||
},
|
||||
sql: `CREATE UNIQUE INDEX IF NOT EXISTS "puq_purchaseorder_parent_po_dbe2929d" ON "purchaseorder" ("parent_po") WHERE parent_po IS NOT NULL`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
@@ -153,109 +49,3 @@ func TestIndexToCreateSQL(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndexEquals(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
a Index
|
||||
b Index
|
||||
equals bool
|
||||
}{
|
||||
{
|
||||
name: "PartialUnique_Same",
|
||||
a: &PartialUniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
Where: `"deleted_at" IS NULL`,
|
||||
},
|
||||
b: &PartialUniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
Where: `"deleted_at" IS NULL`,
|
||||
},
|
||||
equals: true,
|
||||
},
|
||||
{
|
||||
name: "PartialUnique_NormalizedPostgresWhere",
|
||||
a: &PartialUniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
Where: `"deleted_at" IS NULL`,
|
||||
},
|
||||
b: &PartialUniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
Where: `(deleted_at IS NULL)`,
|
||||
},
|
||||
equals: true,
|
||||
},
|
||||
{
|
||||
name: "PartialUnique_DifferentWhere",
|
||||
a: &PartialUniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
Where: `"deleted_at" IS NULL`,
|
||||
},
|
||||
b: &PartialUniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
Where: `"active" = true`,
|
||||
},
|
||||
equals: false,
|
||||
},
|
||||
{
|
||||
name: "PartialUnique_NotEqual_Unique",
|
||||
a: &PartialUniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
Where: `"deleted_at" IS NULL`,
|
||||
},
|
||||
b: &UniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
},
|
||||
equals: false,
|
||||
},
|
||||
{
|
||||
name: "Unique_NotEqual_PartialUnique",
|
||||
a: &UniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
},
|
||||
b: &PartialUniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
Where: `"deleted_at" IS NULL`,
|
||||
},
|
||||
equals: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
assert.Equal(t, testCase.equals, testCase.a.Equals(testCase.b))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPartialUniqueIndexName(t *testing.T) {
|
||||
a := &PartialUniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
Where: `"deleted_at" IS NULL`,
|
||||
}
|
||||
b := &PartialUniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
Where: `(deleted_at IS NULL)`,
|
||||
}
|
||||
c := &PartialUniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
Where: `"active" = true`,
|
||||
}
|
||||
|
||||
assert.Equal(t, "puq_users_email_94610c77", a.Name())
|
||||
assert.Equal(t, a.Name(), b.Name())
|
||||
assert.NotEqual(t, a.Name(), c.Name())
|
||||
}
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
package sqlschema
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type whereNormalizer struct {
|
||||
input string
|
||||
}
|
||||
|
||||
func (n *whereNormalizer) hash() string {
|
||||
hasher := fnv.New32a()
|
||||
_, _ = hasher.Write([]byte(n.normalize()))
|
||||
return fmt.Sprintf("%08x", hasher.Sum32())
|
||||
}
|
||||
|
||||
func (n *whereNormalizer) normalize() string {
|
||||
where := strings.TrimSpace(n.input)
|
||||
where = n.stripOuterParentheses(where)
|
||||
|
||||
var output strings.Builder
|
||||
output.Grow(len(where))
|
||||
|
||||
for i := 0; i < len(where); i++ {
|
||||
switch where[i] {
|
||||
case ' ', '\t', '\n', '\r':
|
||||
if output.Len() > 0 {
|
||||
last := output.String()[output.Len()-1]
|
||||
if last != ' ' {
|
||||
output.WriteByte(' ')
|
||||
}
|
||||
}
|
||||
case '\'':
|
||||
end := n.consumeSingleQuotedLiteral(where, i, &output)
|
||||
i = end
|
||||
case '"':
|
||||
token, end := n.consumeDoubleQuotedToken(where, i)
|
||||
output.WriteString(token)
|
||||
i = end
|
||||
default:
|
||||
output.WriteByte(where[i])
|
||||
}
|
||||
}
|
||||
|
||||
return strings.TrimSpace(output.String())
|
||||
}
|
||||
|
||||
func (n *whereNormalizer) stripOuterParentheses(s string) string {
|
||||
for {
|
||||
s = strings.TrimSpace(s)
|
||||
if len(s) < 2 || s[0] != '(' || s[len(s)-1] != ')' || !n.hasWrappingParentheses(s) {
|
||||
return s
|
||||
}
|
||||
s = s[1 : len(s)-1]
|
||||
}
|
||||
}
|
||||
|
||||
func (n *whereNormalizer) hasWrappingParentheses(s string) bool {
|
||||
depth := 0
|
||||
inSingleQuotedLiteral := false
|
||||
inDoubleQuotedToken := false
|
||||
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch s[i] {
|
||||
case '\'':
|
||||
if inDoubleQuotedToken {
|
||||
continue
|
||||
}
|
||||
if inSingleQuotedLiteral && i+1 < len(s) && s[i+1] == '\'' {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
inSingleQuotedLiteral = !inSingleQuotedLiteral
|
||||
case '"':
|
||||
if inSingleQuotedLiteral {
|
||||
continue
|
||||
}
|
||||
if inDoubleQuotedToken && i+1 < len(s) && s[i+1] == '"' {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
inDoubleQuotedToken = !inDoubleQuotedToken
|
||||
case '(':
|
||||
if inSingleQuotedLiteral || inDoubleQuotedToken {
|
||||
continue
|
||||
}
|
||||
depth++
|
||||
case ')':
|
||||
if inSingleQuotedLiteral || inDoubleQuotedToken {
|
||||
continue
|
||||
}
|
||||
depth--
|
||||
if depth == 0 && i != len(s)-1 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return depth == 0
|
||||
}
|
||||
|
||||
func (n *whereNormalizer) consumeSingleQuotedLiteral(s string, start int, output *strings.Builder) int {
|
||||
output.WriteByte(s[start])
|
||||
for i := start + 1; i < len(s); i++ {
|
||||
output.WriteByte(s[i])
|
||||
if s[i] == '\'' {
|
||||
if i+1 < len(s) && s[i+1] == '\'' {
|
||||
i++
|
||||
output.WriteByte(s[i])
|
||||
continue
|
||||
}
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return len(s) - 1
|
||||
}
|
||||
|
||||
func (n *whereNormalizer) consumeDoubleQuotedToken(s string, start int) (string, int) {
|
||||
var ident strings.Builder
|
||||
|
||||
for i := start + 1; i < len(s); i++ {
|
||||
if s[i] == '"' {
|
||||
if i+1 < len(s) && s[i+1] == '"' {
|
||||
ident.WriteByte('"')
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
if n.isSimpleUnquotedIdentifier(ident.String()) {
|
||||
return ident.String(), i
|
||||
}
|
||||
|
||||
return s[start : i+1], i
|
||||
}
|
||||
|
||||
ident.WriteByte(s[i])
|
||||
}
|
||||
|
||||
return s[start:], len(s) - 1
|
||||
}
|
||||
|
||||
func (n *whereNormalizer) isSimpleUnquotedIdentifier(s string) bool {
|
||||
if s == "" || strings.ToLower(s) != s {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := 0; i < len(s); i++ {
|
||||
ch := s[i]
|
||||
if (ch >= 'a' && ch <= 'z') || ch == '_' {
|
||||
continue
|
||||
}
|
||||
if i > 0 && ch >= '0' && ch <= '9' {
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package sqlschema
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestWhereNormalizerNormalize(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
input string
|
||||
output string
|
||||
}{
|
||||
{
|
||||
name: "BooleanComparison",
|
||||
input: `"active" = true`,
|
||||
output: `active = true`,
|
||||
},
|
||||
{
|
||||
name: "QuotedStringLiteralPreserved",
|
||||
input: `status = 'somewhere'`,
|
||||
output: `status = 'somewhere'`,
|
||||
},
|
||||
{
|
||||
name: "EscapedStringLiteralPreserved",
|
||||
input: `status = 'it''s active'`,
|
||||
output: `status = 'it''s active'`,
|
||||
},
|
||||
{
|
||||
name: "OuterParenthesesRemoved",
|
||||
input: `(("deleted_at" IS NULL))`,
|
||||
output: `deleted_at IS NULL`,
|
||||
},
|
||||
{
|
||||
name: "InnerParenthesesPreserved",
|
||||
input: `("deleted_at" IS NULL OR ("active" = true AND "status" = 'open'))`,
|
||||
output: `deleted_at IS NULL OR (active = true AND status = 'open')`,
|
||||
},
|
||||
{
|
||||
name: "MultipleClausesWhitespaceCollapsed",
|
||||
input: " ( \"deleted_at\" IS NULL \n AND\t\"active\" = true AND status = 'open' ) ",
|
||||
output: `deleted_at IS NULL AND active = true AND status = 'open'`,
|
||||
},
|
||||
{
|
||||
name: "ComplexBooleanClauses",
|
||||
input: `NOT ("deleted_at" IS NOT NULL AND ("active" = false OR "status" = 'archived'))`,
|
||||
output: `NOT (deleted_at IS NOT NULL AND (active = false OR status = 'archived'))`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
assert.Equal(t, testCase.output, (&whereNormalizer{input: testCase.input}).normalize())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1146,100 +1146,3 @@ func TestOperatorAlterTable(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOperatorDiffIndices(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
oldIndices []Index
|
||||
newIndices []Index
|
||||
expectedSQLs [][]byte
|
||||
}{
|
||||
{
|
||||
name: "UniqueToPartialUnique_DropAndCreate",
|
||||
oldIndices: []Index{
|
||||
&UniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
},
|
||||
},
|
||||
newIndices: []Index{
|
||||
&PartialUniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
Where: `"deleted_at" IS NULL`,
|
||||
},
|
||||
},
|
||||
expectedSQLs: [][]byte{
|
||||
[]byte(`DROP INDEX IF EXISTS "uq_users_email"`),
|
||||
[]byte(`CREATE UNIQUE INDEX IF NOT EXISTS "puq_users_email_94610c77" ON "users" ("email") WHERE "deleted_at" IS NULL`),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "PartialUnique_SameWhere_NoOp",
|
||||
oldIndices: []Index{
|
||||
&PartialUniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
Where: `"deleted_at" IS NULL`,
|
||||
},
|
||||
},
|
||||
newIndices: []Index{
|
||||
&PartialUniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
Where: `"deleted_at" IS NULL`,
|
||||
},
|
||||
},
|
||||
expectedSQLs: [][]byte{},
|
||||
},
|
||||
{
|
||||
name: "PartialUnique_NormalizedWhere_NoOp",
|
||||
oldIndices: []Index{
|
||||
&PartialUniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
Where: `(deleted_at IS NULL)`,
|
||||
},
|
||||
},
|
||||
newIndices: []Index{
|
||||
&PartialUniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
Where: `"deleted_at" IS NULL`,
|
||||
},
|
||||
},
|
||||
expectedSQLs: [][]byte{},
|
||||
},
|
||||
{
|
||||
name: "PartialUnique_DifferentWhere_DropAndCreate",
|
||||
oldIndices: []Index{
|
||||
&PartialUniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
Where: `"deleted_at" IS NULL`,
|
||||
},
|
||||
},
|
||||
newIndices: []Index{
|
||||
&PartialUniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
Where: `"active" = true`,
|
||||
},
|
||||
},
|
||||
expectedSQLs: [][]byte{
|
||||
[]byte(`DROP INDEX IF EXISTS "puq_users_email_94610c77"`),
|
||||
[]byte(`CREATE UNIQUE INDEX IF NOT EXISTS "puq_users_email_202121f8" ON "users" ("email") WHERE "active" = true`),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
fmter := NewFormatter(schema.NewNopFormatter().Dialect())
|
||||
operator := NewOperator(fmter, OperatorSupport{})
|
||||
|
||||
actuals := operator.DiffIndices(testCase.oldIndices, testCase.newIndices)
|
||||
assert.Equal(t, testCase.expectedSQLs, actuals)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package sqlitesqlschema
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
@@ -115,29 +114,7 @@ func (provider *provider) GetIndices(ctx context.Context, tableName sqlschema.Ta
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if unique && partial {
|
||||
var indexSQL string
|
||||
if err := provider.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewRaw("SELECT sql FROM sqlite_master WHERE type = 'index' AND name = ?", name).
|
||||
Scan(ctx, &indexSQL); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
where := extractWhereClause(indexSQL)
|
||||
index := &sqlschema.PartialUniqueIndex{
|
||||
TableName: tableName,
|
||||
ColumnNames: columns,
|
||||
Where: where,
|
||||
}
|
||||
|
||||
if index.Name() == name {
|
||||
indices = append(indices, index)
|
||||
} else {
|
||||
indices = append(indices, index.Named(name))
|
||||
}
|
||||
} else if unique {
|
||||
if unique {
|
||||
index := &sqlschema.UniqueIndex{
|
||||
TableName: tableName,
|
||||
ColumnNames: columns,
|
||||
@@ -171,73 +148,3 @@ func (provider *provider) ToggleFKEnforcement(ctx context.Context, db bun.IDB, o
|
||||
|
||||
return errors.NewInternalf(errors.CodeInternal, "foreign_keys(actual: %s, expected: %s), maybe a transaction is in progress?", strconv.FormatBool(val), strconv.FormatBool(on))
|
||||
}
|
||||
|
||||
func extractWhereClause(sql string) string {
|
||||
lastWhere := -1
|
||||
inSingleQuotedLiteral := false
|
||||
inDoubleQuotedIdentifier := false
|
||||
inBacktickQuotedIdentifier := false
|
||||
inBracketQuotedIdentifier := false
|
||||
|
||||
for i := 0; i < len(sql); i++ {
|
||||
switch sql[i] {
|
||||
case '\'':
|
||||
if inDoubleQuotedIdentifier || inBacktickQuotedIdentifier || inBracketQuotedIdentifier {
|
||||
continue
|
||||
}
|
||||
if inSingleQuotedLiteral && i+1 < len(sql) && sql[i+1] == '\'' {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
inSingleQuotedLiteral = !inSingleQuotedLiteral
|
||||
case '"':
|
||||
if inSingleQuotedLiteral || inBacktickQuotedIdentifier || inBracketQuotedIdentifier {
|
||||
continue
|
||||
}
|
||||
if inDoubleQuotedIdentifier && i+1 < len(sql) && sql[i+1] == '"' {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
inDoubleQuotedIdentifier = !inDoubleQuotedIdentifier
|
||||
case '`':
|
||||
if inSingleQuotedLiteral || inDoubleQuotedIdentifier || inBracketQuotedIdentifier {
|
||||
continue
|
||||
}
|
||||
inBacktickQuotedIdentifier = !inBacktickQuotedIdentifier
|
||||
case '[':
|
||||
if inSingleQuotedLiteral || inDoubleQuotedIdentifier || inBacktickQuotedIdentifier || inBracketQuotedIdentifier {
|
||||
continue
|
||||
}
|
||||
inBracketQuotedIdentifier = true
|
||||
case ']':
|
||||
if inBracketQuotedIdentifier {
|
||||
inBracketQuotedIdentifier = false
|
||||
}
|
||||
}
|
||||
|
||||
if inSingleQuotedLiteral || inDoubleQuotedIdentifier || inBacktickQuotedIdentifier || inBracketQuotedIdentifier {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.EqualFold(sql[i:min(i+5, len(sql))], "WHERE") &&
|
||||
(i == 0 || !isSQLiteIdentifierChar(sql[i-1])) &&
|
||||
(i+5 == len(sql) || !isSQLiteIdentifierChar(sql[i+5])) {
|
||||
lastWhere = i
|
||||
i += 4
|
||||
}
|
||||
}
|
||||
|
||||
if lastWhere == -1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return strings.TrimSpace(sql[lastWhere+len("WHERE"):])
|
||||
}
|
||||
|
||||
func isSQLiteIdentifierChar(ch byte) bool {
|
||||
return (ch >= 'a' && ch <= 'z') ||
|
||||
(ch >= 'A' && ch <= 'Z') ||
|
||||
(ch >= '0' && ch <= '9') ||
|
||||
ch == '_'
|
||||
}
|
||||
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
package sqlitesqlschema
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestExtractWhereClause(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
sql string
|
||||
where string
|
||||
}{
|
||||
{
|
||||
name: "UppercaseWhere",
|
||||
sql: `CREATE UNIQUE INDEX "idx" ON "users" ("email") WHERE "deleted_at" IS NULL`,
|
||||
where: `"deleted_at" IS NULL`,
|
||||
},
|
||||
{
|
||||
name: "LowercaseWhere",
|
||||
sql: `CREATE UNIQUE INDEX "idx" ON "users" ("email") where "deleted_at" IS NULL`,
|
||||
where: `"deleted_at" IS NULL`,
|
||||
},
|
||||
{
|
||||
name: "NewlineBeforeWhere",
|
||||
sql: "CREATE UNIQUE INDEX \"idx\" ON \"users\" (\"email\")\nWHERE \"deleted_at\" IS NULL",
|
||||
where: `"deleted_at" IS NULL`,
|
||||
},
|
||||
{
|
||||
name: "ExtraWhitespace",
|
||||
sql: "CREATE UNIQUE INDEX \"idx\" ON \"users\" (\"email\") \n \t where \"deleted_at\" IS NULL ",
|
||||
where: `"deleted_at" IS NULL`,
|
||||
},
|
||||
{
|
||||
name: "WhereInStringLiteral",
|
||||
sql: `CREATE UNIQUE INDEX "idx" ON "users" ("email") WHERE status = 'somewhere'`,
|
||||
where: `status = 'somewhere'`,
|
||||
},
|
||||
{
|
||||
name: "BooleanLiteral",
|
||||
sql: `CREATE UNIQUE INDEX "idx" ON "users" ("email") WHERE active = true`,
|
||||
where: `active = true`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
assert.Equal(t, testCase.where, extractWhereClause(testCase.sql))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -13,9 +13,9 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/statsreporter"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
"github.com/SigNoz/signoz/pkg/tokenizer"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/usertypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/SigNoz/signoz/pkg/version"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
@@ -175,7 +175,7 @@ func (provider *provider) Report(ctx context.Context) error {
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
traits := types.NewTraitsFromUser(user)
|
||||
traits := usertypes.NewTraitsFromUser(user)
|
||||
if maxLastObservedAt, ok := maxLastObservedAtPerUserID[user.ID]; ok {
|
||||
traits["auth_token.last_observed_at.max.time"] = maxLastObservedAt.UTC()
|
||||
traits["auth_token.last_observed_at.max.time_unix"] = maxLastObservedAt.Unix()
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
package markdownrenderer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
)
|
||||
|
||||
// SoftLineBreakHTML is a HTML tag that is used to represent a soft line break.
|
||||
const SoftLineBreakHTML = `<p></p>`
|
||||
|
||||
func (r *markdownRenderer) renderHTML(_ context.Context, markdown string) (string, error) {
|
||||
var buf bytes.Buffer
|
||||
if err := r.htmlRenderer.Convert([]byte(markdown), &buf); err != nil {
|
||||
return "", errors.WrapInternalf(err, errors.CodeInternal, "failed to convert markdown to HTML")
|
||||
}
|
||||
|
||||
// return buf.String(), nil
|
||||
|
||||
// TODO: check if there is another way to handle soft line breaks in HTML
|
||||
// the idea with paragraph tags is that it will start the content in new
|
||||
// line without using a line break tag, this works well in variety of cases
|
||||
// but not all, for example, in case of code block, the paragraph tags will be added
|
||||
// to the code block where newline is present.
|
||||
return strings.ReplaceAll(buf.String(), "\n", SoftLineBreakHTML), nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user