mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 06:55:58 +08:00
feat: enterprise edition (#1575)
* feat: added license manager and feature flags * feat: completed org domain api * chore: checking in saml auth handler code * feat: added signup with sso * feat: added login support for admins * feat: added pem support for certificate * ci(build-workflow): 👷 include EE query-service * fix: 🐛 update package name * chore(ee): 🔧 LD_FLAGS related changes Signed-off-by: Prashant Shahi <prashant@signoz.io> Co-authored-by: Prashant Shahi <prashant@signoz.io> Co-authored-by: nityanandagohain <nityanandagohain@gmail.com>
This commit is contained in:
parent
106033c296
commit
9c4521b34a
6
.dockerignore
Normal file
6
.dockerignore
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
.git
|
||||||
|
.github
|
||||||
|
.vscode
|
||||||
|
README.md
|
||||||
|
deploy
|
||||||
|
sample-apps
|
10
.github/workflows/build.yaml
vendored
10
.github/workflows/build.yaml
vendored
@ -36,3 +36,13 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
make build-query-service-amd64
|
make build-query-service-amd64
|
||||||
|
|
||||||
|
build-ee-query-service:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Build EE query-service image
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
make build-ee-query-service-amd64
|
||||||
|
7
.gitignore
vendored
7
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
node_modules
|
node_modules
|
||||||
yarn.lock
|
yarn.lock
|
||||||
package.json
|
package.json
|
||||||
@ -43,8 +44,12 @@ pkg/query-service/signoz.db
|
|||||||
|
|
||||||
pkg/query-service/tests/test-deploy/data/
|
pkg/query-service/tests/test-deploy/data/
|
||||||
|
|
||||||
|
ee/query-service/signoz.db
|
||||||
|
|
||||||
|
ee/query-service/tests/test-deploy/data/
|
||||||
|
|
||||||
# local data
|
# local data
|
||||||
|
*.db
|
||||||
/deploy/docker/clickhouse-setup/data/
|
/deploy/docker/clickhouse-setup/data/
|
||||||
/deploy/docker-swarm/clickhouse-setup/data/
|
/deploy/docker-swarm/clickhouse-setup/data/
|
||||||
|
bin/
|
54
Makefile
54
Makefile
@ -7,10 +7,12 @@ BUILD_VERSION ?= $(shell git describe --always --tags)
|
|||||||
BUILD_HASH ?= $(shell git rev-parse --short HEAD)
|
BUILD_HASH ?= $(shell git rev-parse --short HEAD)
|
||||||
BUILD_TIME ?= $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
|
BUILD_TIME ?= $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
BUILD_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)
|
BUILD_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)
|
||||||
|
DEV_LICENSE_SIGNOZ_IO ?= https://staging-license.signoz.io/api/v1
|
||||||
|
|
||||||
# Internal variables or constants.
|
# Internal variables or constants.
|
||||||
FRONTEND_DIRECTORY ?= frontend
|
FRONTEND_DIRECTORY ?= frontend
|
||||||
QUERY_SERVICE_DIRECTORY ?= pkg/query-service
|
QUERY_SERVICE_DIRECTORY ?= pkg/query-service
|
||||||
|
EE_QUERY_SERVICE_DIRECTORY ?= ee/query-service
|
||||||
STANDALONE_DIRECTORY ?= deploy/docker/clickhouse-setup
|
STANDALONE_DIRECTORY ?= deploy/docker/clickhouse-setup
|
||||||
SWARM_DIRECTORY ?= deploy/docker-swarm/clickhouse-setup
|
SWARM_DIRECTORY ?= deploy/docker-swarm/clickhouse-setup
|
||||||
LOCAL_GOOS ?= $(shell go env GOOS)
|
LOCAL_GOOS ?= $(shell go env GOOS)
|
||||||
@ -21,15 +23,18 @@ DOCKER_TAG ?= latest
|
|||||||
|
|
||||||
FRONTEND_DOCKER_IMAGE ?= frontend
|
FRONTEND_DOCKER_IMAGE ?= frontend
|
||||||
QUERY_SERVICE_DOCKER_IMAGE ?= query-service
|
QUERY_SERVICE_DOCKER_IMAGE ?= query-service
|
||||||
|
DEV_BUILD ?= ""
|
||||||
|
|
||||||
# Build-time Go variables
|
# Build-time Go variables
|
||||||
PACKAGE?=go.signoz.io/query-service
|
PACKAGE?=go.signoz.io/signoz
|
||||||
buildVersion=${PACKAGE}/version.buildVersion
|
buildVersion=${PACKAGE}/pkg/query-service/version.buildVersion
|
||||||
buildHash=${PACKAGE}/version.buildHash
|
buildHash=${PACKAGE}/pkg/query-service/version.buildHash
|
||||||
buildTime=${PACKAGE}/version.buildTime
|
buildTime=${PACKAGE}/pkg/query-service/version.buildTime
|
||||||
gitBranch=${PACKAGE}/version.gitBranch
|
gitBranch=${PACKAGE}/pkg/query-service/version.gitBranch
|
||||||
|
licenseSignozIo=${PACKAGE}/ee/query-service/constants.LicenseSignozIo
|
||||||
|
|
||||||
LD_FLAGS="-X ${buildHash}=${BUILD_HASH} -X ${buildTime}=${BUILD_TIME} -X ${buildVersion}=${BUILD_VERSION} -X ${gitBranch}=${BUILD_BRANCH}"
|
LD_FLAGS=-X ${buildHash}=${BUILD_HASH} -X ${buildTime}=${BUILD_TIME} -X ${buildVersion}=${BUILD_VERSION} -X ${gitBranch}=${BUILD_BRANCH}
|
||||||
|
DEV_LD_FLAGS=-X ${licenseSignozIo}=${DEV_LICENSE_SIGNOZ_IO}
|
||||||
|
|
||||||
all: build-push-frontend build-push-query-service
|
all: build-push-frontend build-push-query-service
|
||||||
# Steps to build and push docker image of frontend
|
# Steps to build and push docker image of frontend
|
||||||
@ -40,7 +45,7 @@ build-frontend-amd64:
|
|||||||
@echo "--> Building frontend docker image for amd64"
|
@echo "--> Building frontend docker image for amd64"
|
||||||
@echo "------------------"
|
@echo "------------------"
|
||||||
@cd $(FRONTEND_DIRECTORY) && \
|
@cd $(FRONTEND_DIRECTORY) && \
|
||||||
docker build -f Dockerfile --no-cache -t $(REPONAME)/$(FRONTEND_DOCKER_IMAGE):$(DOCKER_TAG) \
|
docker build --file Dockerfile --no-cache -t $(REPONAME)/$(FRONTEND_DOCKER_IMAGE):$(DOCKER_TAG) \
|
||||||
--build-arg TARGETPLATFORM="linux/amd64" .
|
--build-arg TARGETPLATFORM="linux/amd64" .
|
||||||
|
|
||||||
# Step to build and push docker image of frontend(used in push pipeline)
|
# Step to build and push docker image of frontend(used in push pipeline)
|
||||||
@ -59,20 +64,43 @@ build-query-service-amd64:
|
|||||||
@echo "------------------"
|
@echo "------------------"
|
||||||
@echo "--> Building query-service docker image for amd64"
|
@echo "--> Building query-service docker image for amd64"
|
||||||
@echo "------------------"
|
@echo "------------------"
|
||||||
@cd $(QUERY_SERVICE_DIRECTORY) && \
|
@docker build --file $(QUERY_SERVICE_DIRECTORY)/Dockerfile \
|
||||||
docker build -f Dockerfile --no-cache -t $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) \
|
--no-cache -t $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) \
|
||||||
--build-arg TARGETPLATFORM="linux/amd64" --build-arg LD_FLAGS=$(LD_FLAGS) .
|
--build-arg TARGETPLATFORM="linux/amd64" --build-arg LD_FLAGS="$(LD_FLAGS)" .
|
||||||
|
|
||||||
# Step to build and push docker image of query in amd64 and arm64 (used in push pipeline)
|
# Step to build and push docker image of query in amd64 and arm64 (used in push pipeline)
|
||||||
build-push-query-service:
|
build-push-query-service:
|
||||||
@echo "------------------"
|
@echo "------------------"
|
||||||
@echo "--> Building and pushing query-service docker image"
|
@echo "--> Building and pushing query-service docker image"
|
||||||
@echo "------------------"
|
@echo "------------------"
|
||||||
@cd $(QUERY_SERVICE_DIRECTORY) && \
|
@docker buildx build --file $(QUERY_SERVICE_DIRECTORY)/Dockerfile --progress plane --no-cache \
|
||||||
docker buildx build --file Dockerfile --progress plane --no-cache \
|
--push --platform linux/arm64,linux/amd64 --build-arg LD_FLAGS="$(LD_FLAGS)" \
|
||||||
--push --platform linux/arm64,linux/amd64 --build-arg LD_FLAGS=$(LD_FLAGS) \
|
|
||||||
--tag $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) .
|
--tag $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) .
|
||||||
|
|
||||||
|
# Step to build EE docker image of query service in amd64 (used in build pipeline)
|
||||||
|
build-ee-query-service-amd64:
|
||||||
|
@echo "------------------"
|
||||||
|
@echo "--> Building query-service docker image for amd64"
|
||||||
|
@echo "------------------"
|
||||||
|
@if [ $(DEV_BUILD) != "" ]; then \
|
||||||
|
docker build --file $(EE_QUERY_SERVICE_DIRECTORY)/Dockerfile \
|
||||||
|
--no-cache -t $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) \
|
||||||
|
--build-arg TARGETPLATFORM="linux/amd64" --build-arg LD_FLAGS="${LD_FLAGS} ${DEV_LD_FLAGS}" .; \
|
||||||
|
else \
|
||||||
|
docker build --file $(EE_QUERY_SERVICE_DIRECTORY)/Dockerfile \
|
||||||
|
--no-cache -t $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) \
|
||||||
|
--build-arg TARGETPLATFORM="linux/amd64" --build-arg LD_FLAGS="$(LD_FLAGS)" .; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step to build and push EE docker image of query in amd64 and arm64 (used in push pipeline)
|
||||||
|
build-push-ee-query-service:
|
||||||
|
@echo "------------------"
|
||||||
|
@echo "--> Building and pushing query-service docker image"
|
||||||
|
@echo "------------------"
|
||||||
|
@docker buildx build --file $(EE_QUERY_SERVICE_DIRECTORY)/Dockerfile \
|
||||||
|
--progress plane --no-cache --push --platform linux/arm64,linux/amd64 \
|
||||||
|
--build-arg LD_FLAGS="$(LD_FLAGS)" --tag $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) .
|
||||||
|
|
||||||
dev-setup:
|
dev-setup:
|
||||||
mkdir -p /var/lib/signoz
|
mkdir -p /var/lib/signoz
|
||||||
sqlite3 /var/lib/signoz/signoz.db "VACUUM";
|
sqlite3 /var/lib/signoz/signoz.db "VACUUM";
|
||||||
|
4
ee/query-service/.dockerignore
Normal file
4
ee/query-service/.dockerignore
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.vscode
|
||||||
|
README.md
|
||||||
|
signoz.db
|
||||||
|
bin
|
48
ee/query-service/Dockerfile
Normal file
48
ee/query-service/Dockerfile
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
FROM golang:1.17-buster AS builder
|
||||||
|
|
||||||
|
# LD_FLAGS is passed as argument from Makefile. It will be empty, if no argument passed
|
||||||
|
ARG LD_FLAGS
|
||||||
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
|
ENV CGO_ENABLED=1
|
||||||
|
ENV GOPATH=/go
|
||||||
|
|
||||||
|
RUN export GOOS=$(echo ${TARGETPLATFORM} | cut -d / -f1) && \
|
||||||
|
export GOARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2)
|
||||||
|
|
||||||
|
# Prepare and enter src directory
|
||||||
|
WORKDIR /go/src/github.com/signoz/signoz
|
||||||
|
|
||||||
|
# Add the sources and proceed with build
|
||||||
|
ADD . .
|
||||||
|
RUN cd ee/query-service \
|
||||||
|
&& go build -tags timetzdata -a -o ./bin/query-service \
|
||||||
|
-ldflags "-linkmode external -extldflags '-static' -s -w $LD_FLAGS" \
|
||||||
|
&& chmod +x ./bin/query-service
|
||||||
|
|
||||||
|
|
||||||
|
# use a minimal alpine image
|
||||||
|
FROM alpine:3.7
|
||||||
|
|
||||||
|
# Add Maintainer Info
|
||||||
|
LABEL maintainer="signoz"
|
||||||
|
|
||||||
|
# add ca-certificates in case you need them
|
||||||
|
RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
|
# set working directory
|
||||||
|
WORKDIR /root
|
||||||
|
|
||||||
|
# copy the binary from builder
|
||||||
|
COPY --from=builder /go/src/github.com/signoz/signoz/ee/query-service/bin/query-service .
|
||||||
|
|
||||||
|
# copy prometheus YAML config
|
||||||
|
COPY pkg/query-service/config/prometheus.yml /root/config/prometheus.yml
|
||||||
|
|
||||||
|
# run the binary
|
||||||
|
ENTRYPOINT ["./query-service"]
|
||||||
|
|
||||||
|
CMD ["-config", "../config/prometheus.yml"]
|
||||||
|
# CMD ["./query-service -config /root/config/prometheus.yml"]
|
||||||
|
|
||||||
|
EXPOSE 8080
|
124
ee/query-service/app/api/api.go
Normal file
124
ee/query-service/app/api/api.go
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"go.signoz.io/signoz/ee/query-service/dao"
|
||||||
|
"go.signoz.io/signoz/ee/query-service/interfaces"
|
||||||
|
"go.signoz.io/signoz/ee/query-service/license"
|
||||||
|
baseapp "go.signoz.io/signoz/pkg/query-service/app"
|
||||||
|
baseint "go.signoz.io/signoz/pkg/query-service/interfaces"
|
||||||
|
rules "go.signoz.io/signoz/pkg/query-service/rules"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
type APIHandlerOptions struct {
|
||||||
|
DataConnector interfaces.DataConnector
|
||||||
|
AppDao dao.ModelDao
|
||||||
|
RulesManager *rules.Manager
|
||||||
|
FeatureFlags baseint.FeatureLookup
|
||||||
|
LicenseManager *license.Manager
|
||||||
|
}
|
||||||
|
|
||||||
|
type APIHandler struct {
|
||||||
|
opts APIHandlerOptions
|
||||||
|
baseapp.APIHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAPIHandler returns an APIHandler
|
||||||
|
func NewAPIHandler(opts APIHandlerOptions) (*APIHandler, error) {
|
||||||
|
|
||||||
|
baseHandler, err := baseapp.NewAPIHandler(baseapp.APIHandlerOpts{
|
||||||
|
Reader: opts.DataConnector,
|
||||||
|
AppDao: opts.AppDao,
|
||||||
|
RuleManager: opts.RulesManager,
|
||||||
|
FeatureFlags: opts.FeatureFlags})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ah := &APIHandler{
|
||||||
|
opts: opts,
|
||||||
|
APIHandler: *baseHandler,
|
||||||
|
}
|
||||||
|
return ah, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ah *APIHandler) FF() baseint.FeatureLookup {
|
||||||
|
return ah.opts.FeatureFlags
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ah *APIHandler) RM() *rules.Manager {
|
||||||
|
return ah.opts.RulesManager
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ah *APIHandler) LM() *license.Manager {
|
||||||
|
return ah.opts.LicenseManager
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ah *APIHandler) AppDao() dao.ModelDao {
|
||||||
|
return ah.opts.AppDao
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ah *APIHandler) CheckFeature(f string) bool {
|
||||||
|
err := ah.FF().CheckFeature(f)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterRoutes registers routes for this handler on the given router
|
||||||
|
func (ah *APIHandler) RegisterRoutes(router *mux.Router) {
|
||||||
|
// note: add ee override methods first
|
||||||
|
|
||||||
|
// routes available only in ee version
|
||||||
|
router.HandleFunc("/api/v1/licenses",
|
||||||
|
baseapp.AdminAccess(ah.listLicenses)).
|
||||||
|
Methods(http.MethodGet)
|
||||||
|
|
||||||
|
router.HandleFunc("/api/v1/licenses",
|
||||||
|
baseapp.AdminAccess(ah.applyLicense)).
|
||||||
|
Methods(http.MethodPost)
|
||||||
|
|
||||||
|
router.HandleFunc("/api/v1/featureFlags",
|
||||||
|
baseapp.OpenAccess(ah.getFeatureFlags)).
|
||||||
|
Methods(http.MethodGet)
|
||||||
|
|
||||||
|
router.HandleFunc("/api/v1/loginPrecheck",
|
||||||
|
baseapp.OpenAccess(ah.precheckLogin)).
|
||||||
|
Methods(http.MethodGet)
|
||||||
|
|
||||||
|
// paid plans specific routes
|
||||||
|
router.HandleFunc("/api/v1/complete/saml",
|
||||||
|
baseapp.OpenAccess(ah.receiveSAML)).
|
||||||
|
Methods(http.MethodPost)
|
||||||
|
|
||||||
|
router.HandleFunc("/api/v1/orgs/{orgId}/domains",
|
||||||
|
baseapp.AdminAccess(ah.listDomainsByOrg)).
|
||||||
|
Methods(http.MethodGet)
|
||||||
|
|
||||||
|
router.HandleFunc("/api/v1/domains",
|
||||||
|
baseapp.AdminAccess(ah.postDomain)).
|
||||||
|
Methods(http.MethodPost)
|
||||||
|
|
||||||
|
router.HandleFunc("/api/v1/domains/{id}",
|
||||||
|
baseapp.AdminAccess(ah.putDomain)).
|
||||||
|
Methods(http.MethodPut)
|
||||||
|
|
||||||
|
router.HandleFunc("/api/v1/domains/{id}",
|
||||||
|
baseapp.AdminAccess(ah.deleteDomain)).
|
||||||
|
Methods(http.MethodDelete)
|
||||||
|
|
||||||
|
// base overrides
|
||||||
|
router.HandleFunc("/api/v1/version", baseapp.OpenAccess(ah.getVersion)).Methods(http.MethodGet)
|
||||||
|
router.HandleFunc("/api/v1/invite/{token}", baseapp.OpenAccess(ah.getInvite)).Methods(http.MethodGet)
|
||||||
|
router.HandleFunc("/api/v1/register", baseapp.OpenAccess(ah.registerUser)).Methods(http.MethodPost)
|
||||||
|
router.HandleFunc("/api/v1/login", baseapp.OpenAccess(ah.loginUser)).Methods(http.MethodPost)
|
||||||
|
ah.APIHandler.RegisterRoutes(router)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ah *APIHandler) getVersion(w http.ResponseWriter, r *http.Request) {
|
||||||
|
version := version.GetVersion()
|
||||||
|
ah.WriteJSON(w, r, map[string]string{"version": version, "ee": "Y"})
|
||||||
|
}
|
297
ee/query-service/app/api/auth.go
Normal file
297
ee/query-service/app/api/auth.go
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"go.signoz.io/signoz/ee/query-service/constants"
|
||||||
|
"go.signoz.io/signoz/ee/query-service/model"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/auth"
|
||||||
|
baseauth "go.signoz.io/signoz/pkg/query-service/auth"
|
||||||
|
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseRequest(r *http.Request, req interface{}) error {
|
||||||
|
defer r.Body.Close()
|
||||||
|
requestBody, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(requestBody, &req)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// loginUser overrides base handler and considers SSO case.
|
||||||
|
func (ah *APIHandler) loginUser(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
req := basemodel.LoginRequest{}
|
||||||
|
err := parseRequest(r, &req)
|
||||||
|
if err != nil {
|
||||||
|
RespondError(w, model.BadRequest(err), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
if req.Email != "" && ah.CheckFeature(model.SSO) {
|
||||||
|
var apierr basemodel.BaseApiError
|
||||||
|
_, apierr = ah.AppDao().CanUsePassword(ctx, req.Email)
|
||||||
|
if apierr != nil && !apierr.IsNil() {
|
||||||
|
RespondError(w, apierr, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if all looks good, call auth
|
||||||
|
resp, err := auth.Login(ctx, &req)
|
||||||
|
if ah.HandleError(w, err, http.StatusUnauthorized) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ah.WriteJSON(w, r, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// registerUser registers a user and responds with a precheck
|
||||||
|
// so the front-end can decide the login method
|
||||||
|
func (ah *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
if !ah.CheckFeature(model.SSO) {
|
||||||
|
ah.APIHandler.Register(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
var req *baseauth.RegisterRequest
|
||||||
|
|
||||||
|
defer r.Body.Close()
|
||||||
|
requestBody, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf("received no input in api\n", err)
|
||||||
|
RespondError(w, model.BadRequest(err), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(requestBody, &req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf("received invalid user registration request", zap.Error(err))
|
||||||
|
RespondError(w, model.BadRequest(fmt.Errorf("failed to register user")), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get invite object
|
||||||
|
invite, err := baseauth.ValidateInvite(ctx, req)
|
||||||
|
if err != nil || invite == nil {
|
||||||
|
zap.S().Errorf("failed to validate invite token", err)
|
||||||
|
RespondError(w, model.BadRequest(basemodel.ErrSignupFailed{}), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get auth domain from email domain
|
||||||
|
domain, apierr := ah.AppDao().GetDomainByEmail(ctx, invite.Email)
|
||||||
|
if apierr != nil {
|
||||||
|
zap.S().Errorf("failed to get domain from email", apierr)
|
||||||
|
RespondError(w, model.InternalError(basemodel.ErrSignupFailed{}), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
precheckResp := &model.PrecheckResponse{
|
||||||
|
SSO: false,
|
||||||
|
IsUser: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
if domain != nil && domain.SsoEnabled {
|
||||||
|
// so is enabled, create user and respond precheck data
|
||||||
|
user, apierr := baseauth.RegisterInvitedUser(ctx, req, true)
|
||||||
|
if apierr != nil {
|
||||||
|
RespondError(w, apierr, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var precheckError basemodel.BaseApiError
|
||||||
|
|
||||||
|
precheckResp, precheckError = ah.AppDao().PrecheckLogin(ctx, user.Email, req.SourceUrl)
|
||||||
|
if precheckError != nil {
|
||||||
|
RespondError(w, precheckError, precheckResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// no-sso, validate password
|
||||||
|
if err := auth.ValidatePassword(req.Password); err != nil {
|
||||||
|
RespondError(w, model.InternalError(fmt.Errorf("password is not in a valid format")), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, registerError := baseauth.Register(ctx, req)
|
||||||
|
if !registerError.IsNil() {
|
||||||
|
RespondError(w, apierr, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
precheckResp.IsUser = true
|
||||||
|
}
|
||||||
|
|
||||||
|
ah.Respond(w, precheckResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getInvite returns the invite object details for the given invite token. We do not need to
|
||||||
|
// protect this API because invite token itself is meant to be private.
|
||||||
|
func (ah *APIHandler) getInvite(w http.ResponseWriter, r *http.Request) {
|
||||||
|
token := mux.Vars(r)["token"]
|
||||||
|
sourceUrl := r.URL.Query().Get("ref")
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
inviteObject, err := baseauth.GetInvite(context.Background(), token)
|
||||||
|
if err != nil {
|
||||||
|
RespondError(w, model.BadRequest(err), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := model.GettableInvitation{
|
||||||
|
InvitationResponseObject: inviteObject,
|
||||||
|
}
|
||||||
|
|
||||||
|
precheck, apierr := ah.AppDao().PrecheckLogin(ctx, inviteObject.Email, sourceUrl)
|
||||||
|
resp.Precheck = precheck
|
||||||
|
|
||||||
|
if apierr != nil {
|
||||||
|
RespondError(w, apierr, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
ah.WriteJSON(w, r, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrecheckLogin enables browser login page to display appropriate
|
||||||
|
// login methods
|
||||||
|
func (ah *APIHandler) precheckLogin(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
email := r.URL.Query().Get("email")
|
||||||
|
sourceUrl := r.URL.Query().Get("ref")
|
||||||
|
|
||||||
|
resp, apierr := ah.AppDao().PrecheckLogin(ctx, email, sourceUrl)
|
||||||
|
if apierr != nil {
|
||||||
|
RespondError(w, apierr, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
ah.Respond(w, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ah *APIHandler) receiveSAML(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// this is the source url that initiated the login request
|
||||||
|
redirectUri := constants.GetDefaultSiteURL()
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
var apierr basemodel.BaseApiError
|
||||||
|
|
||||||
|
redirectOnError := func() {
|
||||||
|
ssoError := []byte("Login failed. Please contact your system administrator")
|
||||||
|
dst := make([]byte, base64.StdEncoding.EncodedLen(len(ssoError)))
|
||||||
|
base64.StdEncoding.Encode(dst, ssoError)
|
||||||
|
|
||||||
|
http.Redirect(w, r, fmt.Sprintf("%s?ssoerror=%s", redirectUri, string(dst)), http.StatusMovedPermanently)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ah.CheckFeature(model.SSO) {
|
||||||
|
zap.S().Errorf("[ReceiveSAML] sso requested but feature unavailable %s in org domain %s", model.SSO)
|
||||||
|
http.Redirect(w, r, fmt.Sprintf("%s?ssoerror=%s", redirectUri, "feature unavailable, please upgrade your billing plan to access this feature"), http.StatusMovedPermanently)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.ParseForm()
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf("[ReceiveSAML] failed to process response - invalid response from IDP", err, r)
|
||||||
|
redirectOnError()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// the relay state is sent when a login request is submitted to
|
||||||
|
// Idp.
|
||||||
|
relayState := r.FormValue("RelayState")
|
||||||
|
zap.S().Debug("[ReceiveML] relay state", zap.String("relayState", relayState))
|
||||||
|
|
||||||
|
parsedState, err := url.Parse(relayState)
|
||||||
|
if err != nil || relayState == "" {
|
||||||
|
zap.S().Errorf("[ReceiveSAML] failed to process response - invalid response from IDP", err, r)
|
||||||
|
redirectOnError()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// upgrade redirect url from the relay state for better accuracy
|
||||||
|
redirectUri = fmt.Sprintf("%s://%s%s", parsedState.Scheme, parsedState.Host, "/login")
|
||||||
|
|
||||||
|
// derive domain id from relay state now
|
||||||
|
var domainIdStr string
|
||||||
|
for k, v := range parsedState.Query() {
|
||||||
|
if k == "domainId" && len(v) > 0 {
|
||||||
|
domainIdStr = strings.Replace(v[0], ":", "-", -1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
domainId, err := uuid.Parse(domainIdStr)
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf("[ReceiveSAML] failed to process request- failed to parse domain id ifrom relay", zap.Error(err))
|
||||||
|
redirectOnError()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
domain, apierr := ah.AppDao().GetDomain(ctx, domainId)
|
||||||
|
if (apierr != nil) || domain == nil {
|
||||||
|
zap.S().Errorf("[ReceiveSAML] failed to process request- invalid domain", domainIdStr, zap.Error(apierr))
|
||||||
|
redirectOnError()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sp, err := domain.PrepareSamlRequest(parsedState)
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf("[ReceiveSAML] failed to prepare saml request for domain (%s): %v", domainId, err)
|
||||||
|
redirectOnError()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assertionInfo, err := sp.RetrieveAssertionInfo(r.FormValue("SAMLResponse"))
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf("[ReceiveSAML] failed to retrieve assertion info from saml response for organization (%s): %v", domainId, err)
|
||||||
|
redirectOnError()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if assertionInfo.WarningInfo.InvalidTime {
|
||||||
|
zap.S().Errorf("[ReceiveSAML] expired saml response for organization (%s): %v", domainId, err)
|
||||||
|
redirectOnError()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
email := assertionInfo.NameID
|
||||||
|
|
||||||
|
// user email found, now start preparing jwt response
|
||||||
|
userPayload, baseapierr := ah.AppDao().GetUserByEmail(ctx, email)
|
||||||
|
if baseapierr != nil {
|
||||||
|
zap.S().Errorf("[ReceiveSAML] failed to find or register a new user for email %s and org %s", email, domainId, zap.Error(baseapierr.Err))
|
||||||
|
redirectOnError()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenStore, err := baseauth.GenerateJWTForUser(&userPayload.User)
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf("[ReceiveSAML] failed to generate access token for email %s and org %s", email, domainId, zap.Error(err))
|
||||||
|
redirectOnError()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userID := userPayload.User.Id
|
||||||
|
nextPage := fmt.Sprintf("%s?jwt=%s&usr=%s&refreshjwt=%s",
|
||||||
|
redirectUri,
|
||||||
|
tokenStore.AccessJwt,
|
||||||
|
userID,
|
||||||
|
tokenStore.RefreshJwt)
|
||||||
|
|
||||||
|
http.Redirect(w, r, nextPage, http.StatusMovedPermanently)
|
||||||
|
}
|
90
ee/query-service/app/api/domains.go
Normal file
90
ee/query-service/app/api/domains.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"go.signoz.io/signoz/ee/query-service/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ah *APIHandler) listDomainsByOrg(w http.ResponseWriter, r *http.Request) {
|
||||||
|
orgId := mux.Vars(r)["orgId"]
|
||||||
|
domains, apierr := ah.AppDao().ListDomains(context.Background(), orgId)
|
||||||
|
if apierr != nil {
|
||||||
|
RespondError(w, apierr, domains)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ah.Respond(w, domains)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ah *APIHandler) postDomain(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
req := model.OrgDomain{}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
RespondError(w, model.BadRequest(err), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := req.ValidNew(); err != nil {
|
||||||
|
RespondError(w, model.BadRequest(err), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if apierr := ah.AppDao().CreateDomain(ctx, &req); apierr != nil {
|
||||||
|
RespondError(w, apierr, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ah.Respond(w, &req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ah *APIHandler) putDomain(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
domainIdStr := mux.Vars(r)["id"]
|
||||||
|
domainId, err := uuid.Parse(domainIdStr)
|
||||||
|
if err != nil {
|
||||||
|
RespondError(w, model.BadRequest(err), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req := model.OrgDomain{Id: domainId}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
RespondError(w, model.BadRequest(err), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.Id = domainId
|
||||||
|
if err := req.Valid(nil); err != nil {
|
||||||
|
RespondError(w, model.BadRequest(err), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if apierr := ah.AppDao().UpdateDomain(ctx, &req); apierr != nil {
|
||||||
|
RespondError(w, apierr, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ah.Respond(w, &req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ah *APIHandler) deleteDomain(w http.ResponseWriter, r *http.Request) {
|
||||||
|
domainIdStr := mux.Vars(r)["id"]
|
||||||
|
|
||||||
|
domainId, err := uuid.Parse(domainIdStr)
|
||||||
|
if err != nil {
|
||||||
|
RespondError(w, model.BadRequest(fmt.Errorf("invalid domain id")), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apierr := ah.AppDao().DeleteDomain(context.Background(), domainId)
|
||||||
|
if apierr != nil {
|
||||||
|
RespondError(w, apierr, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ah.Respond(w, nil)
|
||||||
|
}
|
10
ee/query-service/app/api/featureFlags.go
Normal file
10
ee/query-service/app/api/featureFlags.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
||||||
|
featureSet := ah.FF().GetFeatureFlags()
|
||||||
|
ah.Respond(w, featureSet)
|
||||||
|
}
|
40
ee/query-service/app/api/license.go
Normal file
40
ee/query-service/app/api/license.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"go.signoz.io/signoz/ee/query-service/model"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ah *APIHandler) listLicenses(w http.ResponseWriter, r *http.Request) {
|
||||||
|
licenses, apiError := ah.LM().GetLicenses(context.Background())
|
||||||
|
if apiError != nil {
|
||||||
|
RespondError(w, apiError, nil)
|
||||||
|
}
|
||||||
|
ah.Respond(w, licenses)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ah *APIHandler) applyLicense(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := context.Background()
|
||||||
|
var l model.License
|
||||||
|
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&l); err != nil {
|
||||||
|
RespondError(w, model.BadRequest(err), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.Key == "" {
|
||||||
|
RespondError(w, model.BadRequest(fmt.Errorf("license key is required")), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
license, apiError := ah.LM().Activate(ctx, l.Key)
|
||||||
|
if apiError != nil {
|
||||||
|
RespondError(w, apiError, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ah.Respond(w, license)
|
||||||
|
}
|
12
ee/query-service/app/api/response.go
Normal file
12
ee/query-service/app/api/response.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
baseapp "go.signoz.io/signoz/pkg/query-service/app"
|
||||||
|
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RespondError(w http.ResponseWriter, apiErr basemodel.BaseApiError, data interface{}) {
|
||||||
|
baseapp.RespondError(w, apiErr, data)
|
||||||
|
}
|
28
ee/query-service/app/db/reader.go
Normal file
28
ee/query-service/app/db/reader.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ClickHouse/clickhouse-go/v2"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
|
||||||
|
basechr "go.signoz.io/signoz/pkg/query-service/app/clickhouseReader"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ClickhouseReader struct {
|
||||||
|
conn clickhouse.Conn
|
||||||
|
appdb *sqlx.DB
|
||||||
|
*basechr.ClickHouseReader
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDataConnector(localDB *sqlx.DB, promConfigPath string) *ClickhouseReader {
|
||||||
|
ch := basechr.NewReader(localDB, promConfigPath)
|
||||||
|
return &ClickhouseReader{
|
||||||
|
conn: ch.GetConn(),
|
||||||
|
appdb: localDB,
|
||||||
|
ClickHouseReader: ch,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ClickhouseReader) Start(readerReady chan bool) {
|
||||||
|
r.ClickHouseReader.Start(readerReady)
|
||||||
|
}
|
442
ee/query-service/app/server.go
Normal file
442
ee/query-service/app/server.go
Normal file
@ -0,0 +1,442 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
_ "net/http/pprof" // http profiler
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/handlers"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
|
||||||
|
"github.com/rs/cors"
|
||||||
|
"github.com/soheilhy/cmux"
|
||||||
|
"go.signoz.io/signoz/ee/query-service/app/api"
|
||||||
|
"go.signoz.io/signoz/ee/query-service/app/db"
|
||||||
|
"go.signoz.io/signoz/ee/query-service/dao"
|
||||||
|
"go.signoz.io/signoz/ee/query-service/interfaces"
|
||||||
|
licensepkg "go.signoz.io/signoz/ee/query-service/license"
|
||||||
|
"go.signoz.io/signoz/ee/query-service/usage"
|
||||||
|
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/app/dashboards"
|
||||||
|
baseconst "go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/healthcheck"
|
||||||
|
basealm "go.signoz.io/signoz/pkg/query-service/integrations/alertManager"
|
||||||
|
baseint "go.signoz.io/signoz/pkg/query-service/interfaces"
|
||||||
|
pqle "go.signoz.io/signoz/pkg/query-service/pqlEngine"
|
||||||
|
rules "go.signoz.io/signoz/pkg/query-service/rules"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/telemetry"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/utils"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServerOptions struct {
|
||||||
|
PromConfigPath string
|
||||||
|
HTTPHostPort string
|
||||||
|
PrivateHostPort string
|
||||||
|
// alert specific params
|
||||||
|
DisableRules bool
|
||||||
|
RuleRepoURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server runs HTTP api service
|
||||||
|
type Server struct {
|
||||||
|
serverOptions *ServerOptions
|
||||||
|
conn net.Listener
|
||||||
|
ruleManager *rules.Manager
|
||||||
|
separatePorts bool
|
||||||
|
|
||||||
|
// public http router
|
||||||
|
httpConn net.Listener
|
||||||
|
httpServer *http.Server
|
||||||
|
|
||||||
|
// private http
|
||||||
|
privateConn net.Listener
|
||||||
|
privateHTTP *http.Server
|
||||||
|
|
||||||
|
// feature flags
|
||||||
|
featureLookup baseint.FeatureLookup
|
||||||
|
|
||||||
|
unavailableChannel chan healthcheck.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
// HealthCheckStatus returns health check status channel a client can subscribe to
|
||||||
|
func (s Server) HealthCheckStatus() chan healthcheck.Status {
|
||||||
|
return s.unavailableChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServer creates and initializes Server
|
||||||
|
func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||||
|
|
||||||
|
modelDao, err := dao.InitDao("sqlite", baseconst.RELATIONAL_DATASOURCE_PATH)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
localDB, err := dashboards.InitDB(baseconst.RELATIONAL_DATASOURCE_PATH)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
localDB.SetMaxOpenConns(10)
|
||||||
|
|
||||||
|
// initiate license manager
|
||||||
|
lm, err := licensepkg.StartManager("sqlite", localDB)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// set license manager as feature flag provider in dao
|
||||||
|
modelDao.SetFlagProvider(lm)
|
||||||
|
readerReady := make(chan bool)
|
||||||
|
|
||||||
|
var reader interfaces.DataConnector
|
||||||
|
storage := os.Getenv("STORAGE")
|
||||||
|
if storage == "clickhouse" {
|
||||||
|
zap.S().Info("Using ClickHouse as datastore ...")
|
||||||
|
qb := db.NewDataConnector(localDB, serverOptions.PromConfigPath)
|
||||||
|
go qb.Start(readerReady)
|
||||||
|
reader = qb
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("Storage type: %s is not supported in query service", storage)
|
||||||
|
}
|
||||||
|
|
||||||
|
<-readerReady
|
||||||
|
rm, err := makeRulesManager(serverOptions.PromConfigPath,
|
||||||
|
baseconst.GetAlertManagerApiPrefix(),
|
||||||
|
serverOptions.RuleRepoURL,
|
||||||
|
localDB,
|
||||||
|
reader,
|
||||||
|
serverOptions.DisableRules)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// start the usagemanager
|
||||||
|
usageManager, err := usage.New("sqlite", localDB, lm.GetRepo(), reader.GetConn())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = usageManager.Start()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
telemetry.GetInstance().SetReader(reader)
|
||||||
|
|
||||||
|
apiOpts := api.APIHandlerOptions{
|
||||||
|
DataConnector: reader,
|
||||||
|
AppDao: modelDao,
|
||||||
|
RulesManager: rm,
|
||||||
|
FeatureFlags: lm,
|
||||||
|
LicenseManager: lm,
|
||||||
|
}
|
||||||
|
|
||||||
|
apiHandler, err := api.NewAPIHandler(apiOpts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &Server{
|
||||||
|
// logger: logger,
|
||||||
|
// tracer: tracer,
|
||||||
|
ruleManager: rm,
|
||||||
|
serverOptions: serverOptions,
|
||||||
|
unavailableChannel: make(chan healthcheck.Status),
|
||||||
|
}
|
||||||
|
|
||||||
|
httpServer, err := s.createPublicServer(apiHandler)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.httpServer = httpServer
|
||||||
|
|
||||||
|
privateServer, err := s.createPrivateServer(apiHandler)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.privateHTTP = privateServer
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) createPrivateServer(apiHandler *api.APIHandler) (*http.Server, error) {
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
|
||||||
|
r.Use(setTimeoutMiddleware)
|
||||||
|
r.Use(s.analyticsMiddleware)
|
||||||
|
r.Use(loggingMiddlewarePrivate)
|
||||||
|
|
||||||
|
apiHandler.RegisterPrivateRoutes(r)
|
||||||
|
|
||||||
|
c := cors.New(cors.Options{
|
||||||
|
//todo(amol): find out a way to add exact domain or
|
||||||
|
// ip here for alert manager
|
||||||
|
AllowedOrigins: []string{"*"},
|
||||||
|
AllowedMethods: []string{"GET", "DELETE", "POST", "PUT", "PATCH"},
|
||||||
|
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type"},
|
||||||
|
})
|
||||||
|
|
||||||
|
handler := c.Handler(r)
|
||||||
|
handler = handlers.CompressHandler(handler)
|
||||||
|
|
||||||
|
return &http.Server{
|
||||||
|
Handler: handler,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) createPublicServer(apiHandler *api.APIHandler) (*http.Server, error) {
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
|
||||||
|
r.Use(setTimeoutMiddleware)
|
||||||
|
r.Use(s.analyticsMiddleware)
|
||||||
|
r.Use(loggingMiddleware)
|
||||||
|
|
||||||
|
apiHandler.RegisterRoutes(r)
|
||||||
|
apiHandler.RegisterMetricsRoutes(r)
|
||||||
|
apiHandler.RegisterLogsRoutes(r)
|
||||||
|
|
||||||
|
c := cors.New(cors.Options{
|
||||||
|
AllowedOrigins: []string{"*"},
|
||||||
|
AllowedMethods: []string{"GET", "DELETE", "POST", "PUT", "PATCH", "OPTIONS"},
|
||||||
|
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "cache-control"},
|
||||||
|
})
|
||||||
|
|
||||||
|
handler := c.Handler(r)
|
||||||
|
|
||||||
|
handler = handlers.CompressHandler(handler)
|
||||||
|
|
||||||
|
return &http.Server{
|
||||||
|
Handler: handler,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loggingMiddleware is used for logging public api calls
|
||||||
|
func loggingMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
route := mux.CurrentRoute(r)
|
||||||
|
path, _ := route.GetPathTemplate()
|
||||||
|
startTime := time.Now()
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
zap.S().Info(path, "\ttimeTaken: ", time.Now().Sub(startTime))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// loggingMiddlewarePrivate is used for logging private api calls
|
||||||
|
// from internal services like alert manager
|
||||||
|
func loggingMiddlewarePrivate(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
route := mux.CurrentRoute(r)
|
||||||
|
path, _ := route.GetPathTemplate()
|
||||||
|
startTime := time.Now()
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
zap.S().Info(path, "\tprivatePort: true", "\ttimeTaken: ", time.Now().Sub(startTime))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type loggingResponseWriter struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
statusCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLoggingResponseWriter(w http.ResponseWriter) *loggingResponseWriter {
|
||||||
|
// WriteHeader(int) is not called if our response implicitly returns 200 OK, so
|
||||||
|
// we default to that status code.
|
||||||
|
return &loggingResponseWriter{w, http.StatusOK}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lrw *loggingResponseWriter) WriteHeader(code int) {
|
||||||
|
lrw.statusCode = code
|
||||||
|
lrw.ResponseWriter.WriteHeader(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush implements the http.Flush interface.
|
||||||
|
func (lrw *loggingResponseWriter) Flush() {
|
||||||
|
lrw.ResponseWriter.(http.Flusher).Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) analyticsMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
route := mux.CurrentRoute(r)
|
||||||
|
path, _ := route.GetPathTemplate()
|
||||||
|
|
||||||
|
lrw := NewLoggingResponseWriter(w)
|
||||||
|
next.ServeHTTP(lrw, r)
|
||||||
|
|
||||||
|
data := map[string]interface{}{"path": path, "statusCode": lrw.statusCode}
|
||||||
|
|
||||||
|
if _, ok := telemetry.IgnoredPaths()[path]; !ok {
|
||||||
|
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_PATH, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func setTimeoutMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
// check if route is not excluded
|
||||||
|
url := r.URL.Path
|
||||||
|
if _, ok := baseconst.TimeoutExcludedRoutes[url]; !ok {
|
||||||
|
ctx, cancel = context.WithTimeout(r.Context(), baseconst.ContextTimeout*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
r = r.WithContext(ctx)
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// initListeners initialises listeners of the server
|
||||||
|
func (s *Server) initListeners() error {
|
||||||
|
// listen on public port
|
||||||
|
var err error
|
||||||
|
publicHostPort := s.serverOptions.HTTPHostPort
|
||||||
|
if publicHostPort == "" {
|
||||||
|
return fmt.Errorf("baseconst.HTTPHostPort is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.httpConn, err = net.Listen("tcp", publicHostPort)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
zap.S().Info(fmt.Sprintf("Query server started listening on %s...", s.serverOptions.HTTPHostPort))
|
||||||
|
|
||||||
|
// listen on private port to support internal services
|
||||||
|
privateHostPort := s.serverOptions.PrivateHostPort
|
||||||
|
|
||||||
|
if privateHostPort == "" {
|
||||||
|
return fmt.Errorf("baseconst.PrivateHostPort is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.privateConn, err = net.Listen("tcp", privateHostPort)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
zap.S().Info(fmt.Sprintf("Query server started listening on private port %s...", s.serverOptions.PrivateHostPort))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start listening on http and private http port concurrently
|
||||||
|
func (s *Server) Start() error {
|
||||||
|
|
||||||
|
// initiate rule manager first
|
||||||
|
if !s.serverOptions.DisableRules {
|
||||||
|
s.ruleManager.Start()
|
||||||
|
} else {
|
||||||
|
zap.S().Info("msg: Rules disabled as rules.disable is set to TRUE")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.initListeners()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var httpPort int
|
||||||
|
if port, err := utils.GetPort(s.httpConn.Addr()); err == nil {
|
||||||
|
httpPort = port
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
zap.S().Info("Starting HTTP server", zap.Int("port", httpPort), zap.String("addr", s.serverOptions.HTTPHostPort))
|
||||||
|
|
||||||
|
switch err := s.httpServer.Serve(s.httpConn); err {
|
||||||
|
case nil, http.ErrServerClosed, cmux.ErrListenerClosed:
|
||||||
|
// normal exit, nothing to do
|
||||||
|
default:
|
||||||
|
zap.S().Error("Could not start HTTP server", zap.Error(err))
|
||||||
|
}
|
||||||
|
s.unavailableChannel <- healthcheck.Unavailable
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
zap.S().Info("Starting pprof server", zap.String("addr", baseconst.DebugHttpPort))
|
||||||
|
|
||||||
|
err = http.ListenAndServe(baseconst.DebugHttpPort, nil)
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Error("Could not start pprof server", zap.Error(err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var privatePort int
|
||||||
|
if port, err := utils.GetPort(s.privateConn.Addr()); err == nil {
|
||||||
|
privatePort = port
|
||||||
|
}
|
||||||
|
fmt.Println("starting private http")
|
||||||
|
go func() {
|
||||||
|
zap.S().Info("Starting Private HTTP server", zap.Int("port", privatePort), zap.String("addr", s.serverOptions.PrivateHostPort))
|
||||||
|
|
||||||
|
switch err := s.privateHTTP.Serve(s.privateConn); err {
|
||||||
|
case nil, http.ErrServerClosed, cmux.ErrListenerClosed:
|
||||||
|
// normal exit, nothing to do
|
||||||
|
zap.S().Info("private http server closed")
|
||||||
|
default:
|
||||||
|
zap.S().Error("Could not start private HTTP server", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
s.unavailableChannel <- healthcheck.Unavailable
|
||||||
|
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeRulesManager(
|
||||||
|
promConfigPath,
|
||||||
|
alertManagerURL string,
|
||||||
|
ruleRepoURL string,
|
||||||
|
db *sqlx.DB,
|
||||||
|
ch baseint.Reader,
|
||||||
|
disableRules bool) (*rules.Manager, error) {
|
||||||
|
|
||||||
|
// create engine
|
||||||
|
pqle, err := pqle.FromConfigPath(promConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create pql engine : %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// notifier opts
|
||||||
|
notifierOpts := basealm.NotifierOptions{
|
||||||
|
QueueCapacity: 10000,
|
||||||
|
Timeout: 1 * time.Second,
|
||||||
|
AlertManagerURLs: []string{alertManagerURL},
|
||||||
|
}
|
||||||
|
|
||||||
|
// create manager opts
|
||||||
|
managerOpts := &rules.ManagerOptions{
|
||||||
|
NotifierOpts: notifierOpts,
|
||||||
|
Queriers: &rules.Queriers{
|
||||||
|
PqlEngine: pqle,
|
||||||
|
Ch: ch.GetConn(),
|
||||||
|
},
|
||||||
|
RepoURL: ruleRepoURL,
|
||||||
|
DBConn: db,
|
||||||
|
Context: context.Background(),
|
||||||
|
Logger: nil,
|
||||||
|
DisableRules: disableRules,
|
||||||
|
}
|
||||||
|
|
||||||
|
// create Manager
|
||||||
|
manager, err := rules.NewManager(managerOpts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("rule manager error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
zap.S().Info("rules manager is ready")
|
||||||
|
|
||||||
|
return manager, nil
|
||||||
|
}
|
28
ee/query-service/constants/constants.go
Normal file
28
ee/query-service/constants/constants.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package constants
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultSiteURL = "https://localhost:3301"
|
||||||
|
)
|
||||||
|
|
||||||
|
var LicenseSignozIo = "https://license.signoz.io/api/v1"
|
||||||
|
|
||||||
|
func GetOrDefaultEnv(key string, fallback string) string {
|
||||||
|
v := os.Getenv(key)
|
||||||
|
if len(v) == 0 {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// constant functions that override env vars
|
||||||
|
|
||||||
|
// GetDefaultSiteURL returns default site url, primarily
|
||||||
|
// used to send saml request and allowing backend to
|
||||||
|
// handle http redirect
|
||||||
|
func GetDefaultSiteURL() string {
|
||||||
|
return GetOrDefaultEnv("SIGNOZ_SITE_URL", DefaultSiteURL)
|
||||||
|
}
|
18
ee/query-service/dao/factory.go
Normal file
18
ee/query-service/dao/factory.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package dao
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"go.signoz.io/signoz/ee/query-service/dao/sqlite"
|
||||||
|
)
|
||||||
|
|
||||||
|
func InitDao(engine, path string) (ModelDao, error) {
|
||||||
|
|
||||||
|
switch engine {
|
||||||
|
case "sqlite":
|
||||||
|
return sqlite.InitDB(path)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("qsdb type: %s is not supported in query service", engine)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
33
ee/query-service/dao/interface.go
Normal file
33
ee/query-service/dao/interface.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package dao
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"go.signoz.io/signoz/ee/query-service/model"
|
||||||
|
basedao "go.signoz.io/signoz/pkg/query-service/dao"
|
||||||
|
baseint "go.signoz.io/signoz/pkg/query-service/interfaces"
|
||||||
|
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ModelDao interface {
|
||||||
|
basedao.ModelDao
|
||||||
|
|
||||||
|
// SetFlagProvider sets the feature lookup provider
|
||||||
|
SetFlagProvider(flags baseint.FeatureLookup)
|
||||||
|
|
||||||
|
DB() *sqlx.DB
|
||||||
|
|
||||||
|
// auth methods
|
||||||
|
PrecheckLogin(ctx context.Context, email, sourceUrl string) (*model.PrecheckResponse, basemodel.BaseApiError)
|
||||||
|
CanUsePassword(ctx context.Context, email string) (bool, basemodel.BaseApiError)
|
||||||
|
|
||||||
|
// org domain (auth domains) CRUD ops
|
||||||
|
ListDomains(ctx context.Context, orgId string) ([]model.OrgDomain, basemodel.BaseApiError)
|
||||||
|
GetDomain(ctx context.Context, id uuid.UUID) (*model.OrgDomain, basemodel.BaseApiError)
|
||||||
|
CreateDomain(ctx context.Context, d *model.OrgDomain) basemodel.BaseApiError
|
||||||
|
UpdateDomain(ctx context.Context, domain *model.OrgDomain) basemodel.BaseApiError
|
||||||
|
DeleteDomain(ctx context.Context, id uuid.UUID) basemodel.BaseApiError
|
||||||
|
GetDomainByEmail(ctx context.Context, email string) (*model.OrgDomain, basemodel.BaseApiError)
|
||||||
|
}
|
112
ee/query-service/dao/sqlite/auth.go
Normal file
112
ee/query-service/dao/sqlite/auth.go
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"go.signoz.io/signoz/ee/query-service/constants"
|
||||||
|
"go.signoz.io/signoz/ee/query-service/model"
|
||||||
|
baseconst "go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
|
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *modelDao) CanUsePassword(ctx context.Context, email string) (bool, basemodel.BaseApiError) {
|
||||||
|
domain, apierr := m.GetDomainByEmail(ctx, email)
|
||||||
|
if apierr != nil {
|
||||||
|
return false, apierr
|
||||||
|
}
|
||||||
|
|
||||||
|
if domain != nil && domain.SsoEnabled {
|
||||||
|
// sso is enabled, check if the user has admin role
|
||||||
|
userPayload, baseapierr := m.GetUserByEmail(ctx, email)
|
||||||
|
|
||||||
|
if baseapierr != nil || userPayload == nil {
|
||||||
|
return false, baseapierr
|
||||||
|
}
|
||||||
|
|
||||||
|
if userPayload.Role != baseconst.AdminGroup {
|
||||||
|
return false, model.BadRequest(fmt.Errorf("auth method not supported"))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrecheckLogin is called when the login or signup page is loaded
|
||||||
|
// to check sso login is to be prompted
|
||||||
|
func (m *modelDao) PrecheckLogin(ctx context.Context, email, sourceUrl string) (*model.PrecheckResponse, basemodel.BaseApiError) {
|
||||||
|
|
||||||
|
// assume user is valid unless proven otherwise
|
||||||
|
resp := &model.PrecheckResponse{IsUser: true, CanSelfRegister: false}
|
||||||
|
|
||||||
|
// check if email is a valid user
|
||||||
|
userPayload, baseApiErr := m.GetUserByEmail(ctx, email)
|
||||||
|
if baseApiErr != nil {
|
||||||
|
return resp, baseApiErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if userPayload == nil {
|
||||||
|
resp.IsUser = false
|
||||||
|
}
|
||||||
|
ssoAvailable := true
|
||||||
|
err := m.checkFeature(model.SSO)
|
||||||
|
if err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case basemodel.ErrFeatureUnavailable:
|
||||||
|
// do nothing, just skip sso
|
||||||
|
ssoAvailable = false
|
||||||
|
default:
|
||||||
|
zap.S().Errorf("feature check failed", zap.String("featureKey", model.SSO), zap.Error(err))
|
||||||
|
return resp, model.BadRequest(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ssoAvailable {
|
||||||
|
|
||||||
|
// find domain from email
|
||||||
|
orgDomain, apierr := m.GetDomainByEmail(ctx, email)
|
||||||
|
if apierr != nil {
|
||||||
|
var emailDomain string
|
||||||
|
emailComponents := strings.Split(email, "@")
|
||||||
|
if len(emailComponents) > 0 {
|
||||||
|
emailDomain = emailComponents[1]
|
||||||
|
}
|
||||||
|
zap.S().Errorf("failed to get org domain from email", zap.String("emailDomain", emailDomain), apierr.ToError())
|
||||||
|
return resp, apierr
|
||||||
|
}
|
||||||
|
|
||||||
|
if orgDomain != nil && orgDomain.SsoEnabled {
|
||||||
|
// saml is enabled for this domain, lets prepare sso url
|
||||||
|
|
||||||
|
if sourceUrl == "" {
|
||||||
|
sourceUrl = constants.GetDefaultSiteURL()
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse source url that generated the login request
|
||||||
|
var err error
|
||||||
|
escapedUrl, _ := url.QueryUnescape(sourceUrl)
|
||||||
|
siteUrl, err := url.Parse(escapedUrl)
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf("failed to parse referer", err)
|
||||||
|
return resp, model.InternalError(fmt.Errorf("failed to generate login request"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// build Idp URL that will authenticat the user
|
||||||
|
// the front-end will redirect user to this url
|
||||||
|
resp.SsoUrl, err = orgDomain.BuildSsoUrl(siteUrl)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf("failed to prepare saml request for domain", zap.String("domain", orgDomain.Name), err)
|
||||||
|
return resp, model.InternalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set SSO to true, as the url is generated correctly
|
||||||
|
resp.SSO = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
183
ee/query-service/dao/sqlite/domain.go
Normal file
183
ee/query-service/dao/sqlite/domain.go
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"go.signoz.io/signoz/ee/query-service/model"
|
||||||
|
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StoredDomain represents stored database record for org domain
|
||||||
|
|
||||||
|
type StoredDomain struct {
|
||||||
|
Id uuid.UUID `db:"id"`
|
||||||
|
Name string `db:"name"`
|
||||||
|
OrgId string `db:"org_id"`
|
||||||
|
Data string `db:"data"`
|
||||||
|
CreatedAt int64 `db:"created_at"`
|
||||||
|
UpdatedAt int64 `db:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDomain returns org domain for a given domain id
|
||||||
|
func (m *modelDao) GetDomain(ctx context.Context, id uuid.UUID) (*model.OrgDomain, basemodel.BaseApiError) {
|
||||||
|
|
||||||
|
stored := StoredDomain{}
|
||||||
|
err := m.DB().Get(&stored, `SELECT * FROM org_domains WHERE id=$1 LIMIT 1`, id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return nil, model.BadRequest(fmt.Errorf("invalid domain id"))
|
||||||
|
}
|
||||||
|
return nil, model.InternalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
domain := &model.OrgDomain{Id: stored.Id, Name: stored.Name, OrgId: stored.OrgId}
|
||||||
|
if err := domain.LoadConfig(stored.Data); err != nil {
|
||||||
|
return domain, model.InternalError(err)
|
||||||
|
}
|
||||||
|
return domain, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListDomains gets the list of auth domains by org id
|
||||||
|
func (m *modelDao) ListDomains(ctx context.Context, orgId string) ([]model.OrgDomain, basemodel.BaseApiError) {
|
||||||
|
domains := []model.OrgDomain{}
|
||||||
|
|
||||||
|
stored := []StoredDomain{}
|
||||||
|
err := m.DB().SelectContext(ctx, &stored, `SELECT * FROM org_domains WHERE org_id=$1`, orgId)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return []model.OrgDomain{}, nil
|
||||||
|
}
|
||||||
|
return nil, model.InternalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range stored {
|
||||||
|
domain := model.OrgDomain{Id: s.Id, Name: s.Name, OrgId: s.OrgId}
|
||||||
|
if err := domain.LoadConfig(s.Data); err != nil {
|
||||||
|
zap.S().Errorf("ListDomains() failed", zap.Error(err))
|
||||||
|
}
|
||||||
|
domains = append(domains, domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
return domains, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDomain creates a new auth domain
|
||||||
|
func (m *modelDao) CreateDomain(ctx context.Context, domain *model.OrgDomain) basemodel.BaseApiError {
|
||||||
|
|
||||||
|
if domain.Id == uuid.Nil {
|
||||||
|
domain.Id = uuid.New()
|
||||||
|
}
|
||||||
|
|
||||||
|
if domain.OrgId == "" || domain.Name == "" {
|
||||||
|
return model.BadRequest(fmt.Errorf("domain creation failed, missing fields: OrgId, Name "))
|
||||||
|
}
|
||||||
|
|
||||||
|
configJson, err := json.Marshal(domain)
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf("failed to unmarshal domain config", zap.Error(err))
|
||||||
|
return model.InternalError(fmt.Errorf("domain creation failed"))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = m.DB().ExecContext(ctx,
|
||||||
|
"INSERT INTO org_domains (id, name, org_id, data, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6)",
|
||||||
|
domain.Id,
|
||||||
|
domain.Name,
|
||||||
|
domain.OrgId,
|
||||||
|
configJson,
|
||||||
|
time.Now().Unix(),
|
||||||
|
time.Now().Unix())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf("failed to insert domain in db", zap.Error(err))
|
||||||
|
return model.InternalError(fmt.Errorf("domain creation failed"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDomain updates stored config params for a domain
|
||||||
|
func (m *modelDao) UpdateDomain(ctx context.Context, domain *model.OrgDomain) basemodel.BaseApiError {
|
||||||
|
|
||||||
|
if domain.Id == uuid.Nil {
|
||||||
|
zap.S().Errorf("domain update failed", zap.Error(fmt.Errorf("OrgDomain.Id is null")))
|
||||||
|
return model.InternalError(fmt.Errorf("domain update failed"))
|
||||||
|
}
|
||||||
|
|
||||||
|
configJson, err := json.Marshal(domain)
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf("domain update failed", zap.Error(err))
|
||||||
|
return model.InternalError(fmt.Errorf("domain update failed"))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = m.DB().ExecContext(ctx,
|
||||||
|
"UPDATE org_domains SET data = $1, updated_at = $2 WHERE id = $3",
|
||||||
|
configJson,
|
||||||
|
time.Now().Unix(),
|
||||||
|
domain.Id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf("domain update failed", zap.Error(err))
|
||||||
|
return model.InternalError(fmt.Errorf("domain update failed"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteDomain deletes an org domain
|
||||||
|
func (m *modelDao) DeleteDomain(ctx context.Context, id uuid.UUID) basemodel.BaseApiError {
|
||||||
|
|
||||||
|
if id == uuid.Nil {
|
||||||
|
zap.S().Errorf("domain delete failed", zap.Error(fmt.Errorf("OrgDomain.Id is null")))
|
||||||
|
return model.InternalError(fmt.Errorf("domain delete failed"))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := m.DB().ExecContext(ctx,
|
||||||
|
"DELETE FROM org_domains WHERE id = $1",
|
||||||
|
id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf("domain delete failed", zap.Error(err))
|
||||||
|
return model.InternalError(fmt.Errorf("domain delete failed"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *modelDao) GetDomainByEmail(ctx context.Context, email string) (*model.OrgDomain, basemodel.BaseApiError) {
|
||||||
|
|
||||||
|
if email == "" {
|
||||||
|
return nil, model.BadRequest(fmt.Errorf("could not find auth domain, missing fields: email "))
|
||||||
|
}
|
||||||
|
|
||||||
|
components := strings.Split(email, "@")
|
||||||
|
if len(components) < 2 {
|
||||||
|
return nil, model.BadRequest(fmt.Errorf("invalid email address"))
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedDomain := components[1]
|
||||||
|
|
||||||
|
stored := StoredDomain{}
|
||||||
|
err := m.DB().Get(&stored, `SELECT * FROM org_domains WHERE name=$1 LIMIT 1`, parsedDomain)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, model.InternalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
domain := &model.OrgDomain{Id: stored.Id, Name: stored.Name, OrgId: stored.OrgId}
|
||||||
|
if err := domain.LoadConfig(stored.Data); err != nil {
|
||||||
|
return domain, model.InternalError(err)
|
||||||
|
}
|
||||||
|
return domain, nil
|
||||||
|
}
|
63
ee/query-service/dao/sqlite/modelDao.go
Normal file
63
ee/query-service/dao/sqlite/modelDao.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
basedao "go.signoz.io/signoz/pkg/query-service/dao"
|
||||||
|
basedsql "go.signoz.io/signoz/pkg/query-service/dao/sqlite"
|
||||||
|
baseint "go.signoz.io/signoz/pkg/query-service/interfaces"
|
||||||
|
)
|
||||||
|
|
||||||
|
type modelDao struct {
|
||||||
|
*basedsql.ModelDaoSqlite
|
||||||
|
flags baseint.FeatureLookup
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFlagProvider sets the feature lookup provider
|
||||||
|
func (m *modelDao) SetFlagProvider(flags baseint.FeatureLookup) {
|
||||||
|
m.flags = flags
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckFeature confirms if a feature is available
|
||||||
|
func (m *modelDao) checkFeature(key string) error {
|
||||||
|
if m.flags == nil {
|
||||||
|
return fmt.Errorf("flag provider not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.flags.CheckFeature(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitDB creates and extends base model DB repository
|
||||||
|
func InitDB(dataSourceName string) (*modelDao, error) {
|
||||||
|
dao, err := basedsql.InitDB(dataSourceName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// set package variable so dependent base methods (e.g. AuthCache) will work
|
||||||
|
basedao.SetDB(dao)
|
||||||
|
m := &modelDao{ModelDaoSqlite: dao}
|
||||||
|
|
||||||
|
table_schema := `
|
||||||
|
PRAGMA foreign_keys = ON;
|
||||||
|
CREATE TABLE IF NOT EXISTS org_domains(
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
org_id TEXT NOT NULL,
|
||||||
|
name VARCHAR(50) NOT NULL UNIQUE,
|
||||||
|
created_at INTEGER NOT NULL,
|
||||||
|
updated_at INTEGER,
|
||||||
|
data TEXT NOT NULL,
|
||||||
|
FOREIGN KEY(org_id) REFERENCES organizations(id)
|
||||||
|
);`
|
||||||
|
|
||||||
|
_, err = m.DB().Exec(table_schema)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error in creating tables: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *modelDao) DB() *sqlx.DB {
|
||||||
|
return m.ModelDaoSqlite.DB()
|
||||||
|
}
|
20
ee/query-service/integrations/signozio/response.go
Normal file
20
ee/query-service/integrations/signozio/response.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package signozio
|
||||||
|
|
||||||
|
type status string
|
||||||
|
|
||||||
|
const (
|
||||||
|
statusSuccess status = "success"
|
||||||
|
statusError status = "error"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ActivationResult struct {
|
||||||
|
Status status `json:"status"`
|
||||||
|
Data *ActivationResponse `json:"data,omitempty"`
|
||||||
|
ErrorType string `json:"errorType,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActivationResponse struct {
|
||||||
|
ActivationId string `json:"ActivationId"`
|
||||||
|
PlanDetails string `json:"PlanDetails"`
|
||||||
|
}
|
159
ee/query-service/integrations/signozio/signozio.go
Normal file
159
ee/query-service/integrations/signozio/signozio.go
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
package signozio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"go.signoz.io/signoz/ee/query-service/constants"
|
||||||
|
"go.signoz.io/signoz/ee/query-service/model"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
var C *Client
|
||||||
|
|
||||||
|
const (
|
||||||
|
POST = "POST"
|
||||||
|
APPLICATION_JSON = "application/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
Prefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *Client {
|
||||||
|
return &Client{
|
||||||
|
Prefix: constants.LicenseSignozIo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
C = New()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActivateLicense sends key to license.signoz.io and gets activation data
|
||||||
|
func ActivateLicense(key, siteId string) (*ActivationResponse, *model.ApiError) {
|
||||||
|
licenseReq := map[string]string{
|
||||||
|
"key": key,
|
||||||
|
"siteId": siteId,
|
||||||
|
}
|
||||||
|
|
||||||
|
reqString, _ := json.Marshal(licenseReq)
|
||||||
|
httpResponse, err := http.Post(C.Prefix+"/licenses/activate", APPLICATION_JSON, bytes.NewBuffer(reqString))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf("failed to connect to license.signoz.io", err)
|
||||||
|
return nil, model.BadRequest(fmt.Errorf("unable to connect with license.signoz.io, please check your network connection"))
|
||||||
|
}
|
||||||
|
|
||||||
|
httpBody, err := ioutil.ReadAll(httpResponse.Body)
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf("failed to read activation response from license.signoz.io", err)
|
||||||
|
return nil, model.BadRequest(fmt.Errorf("failed to read activation response from license.signoz.io"))
|
||||||
|
}
|
||||||
|
|
||||||
|
defer httpResponse.Body.Close()
|
||||||
|
|
||||||
|
// read api request result
|
||||||
|
result := ActivationResult{}
|
||||||
|
err = json.Unmarshal(httpBody, &result)
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf("failed to marshal activation response from license.signoz.io", err)
|
||||||
|
return nil, model.InternalError(errors.Wrap(err, "failed to marshal license activation response"))
|
||||||
|
}
|
||||||
|
|
||||||
|
switch httpResponse.StatusCode {
|
||||||
|
case 200, 201:
|
||||||
|
return result.Data, nil
|
||||||
|
case 400, 401:
|
||||||
|
return nil, model.BadRequest(fmt.Errorf(fmt.Sprintf("failed to activate: %s", result.Error)))
|
||||||
|
default:
|
||||||
|
return nil, model.InternalError(fmt.Errorf(fmt.Sprintf("failed to activate: %s", result.Error)))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateLicense validates the license key
|
||||||
|
func ValidateLicense(activationId string) (*ActivationResponse, *model.ApiError) {
|
||||||
|
validReq := map[string]string{
|
||||||
|
"activationId": activationId,
|
||||||
|
}
|
||||||
|
|
||||||
|
reqString, _ := json.Marshal(validReq)
|
||||||
|
response, err := http.Post(C.Prefix+"/licenses/validate", APPLICATION_JSON, bytes.NewBuffer(reqString))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.BadRequest(errors.Wrap(err, "unable to connect with license.signoz.io, please check your network connection"))
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.BadRequest(errors.Wrap(err, "failed to read validation response from license.signoz.io"))
|
||||||
|
}
|
||||||
|
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
switch response.StatusCode {
|
||||||
|
case 200, 201:
|
||||||
|
a := ActivationResult{}
|
||||||
|
err = json.Unmarshal(body, &a)
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.BadRequest(errors.Wrap(err, "failed to marshal license validation response"))
|
||||||
|
}
|
||||||
|
return a.Data, nil
|
||||||
|
case 400, 401:
|
||||||
|
return nil, model.BadRequest(errors.Wrap(fmt.Errorf(string(body)),
|
||||||
|
"bad request error received from license.signoz.io"))
|
||||||
|
default:
|
||||||
|
return nil, model.InternalError(errors.Wrap(fmt.Errorf(string(body)),
|
||||||
|
"internal error received from license.signoz.io"))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPostRequestWithCtx(ctx context.Context, url string, contentType string, body io.Reader) (*http.Request, error) {
|
||||||
|
req, err := http.NewRequestWithContext(ctx, POST, url, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Add("Content-Type", contentType)
|
||||||
|
return req, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendUsage reports the usage of signoz to license server
|
||||||
|
func SendUsage(ctx context.Context, usage *model.UsagePayload) *model.ApiError {
|
||||||
|
reqString, _ := json.Marshal(usage)
|
||||||
|
req, err := NewPostRequestWithCtx(ctx, C.Prefix+"/usage", APPLICATION_JSON, bytes.NewBuffer(reqString))
|
||||||
|
if err != nil {
|
||||||
|
return model.BadRequest(errors.Wrap(err, "unable to create http request"))
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return model.BadRequest(errors.Wrap(err, "unable to connect with license.signoz.io, please check your network connection"))
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return model.BadRequest(errors.Wrap(err, "failed to read usage response from license.signoz.io"))
|
||||||
|
}
|
||||||
|
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
switch res.StatusCode {
|
||||||
|
case 200, 201:
|
||||||
|
return nil
|
||||||
|
case 400, 401:
|
||||||
|
return model.BadRequest(errors.Wrap(fmt.Errorf(string(body)),
|
||||||
|
"bad request error received from license.signoz.io"))
|
||||||
|
default:
|
||||||
|
return model.InternalError(errors.Wrap(fmt.Errorf(string(body)),
|
||||||
|
"internal error received from license.signoz.io"))
|
||||||
|
}
|
||||||
|
}
|
12
ee/query-service/interfaces/connector.go
Normal file
12
ee/query-service/interfaces/connector.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package interfaces
|
||||||
|
|
||||||
|
import (
|
||||||
|
baseint "go.signoz.io/signoz/pkg/query-service/interfaces"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Connector defines methods for interaction
|
||||||
|
// with o11y data. for example - clickhouse
|
||||||
|
type DataConnector interface {
|
||||||
|
Start(readerReady chan bool)
|
||||||
|
baseint.Reader
|
||||||
|
}
|
127
ee/query-service/license/db.go
Normal file
127
ee/query-service/license/db.go
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
package license
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
|
||||||
|
"go.signoz.io/signoz/ee/query-service/license/sqlite"
|
||||||
|
"go.signoz.io/signoz/ee/query-service/model"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Repo is license repo. stores license keys in a secured DB
|
||||||
|
type Repo struct {
|
||||||
|
db *sqlx.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLicenseRepo initiates a new license repo
|
||||||
|
func NewLicenseRepo(db *sqlx.DB) Repo {
|
||||||
|
return Repo{
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repo) InitDB(engine string) error {
|
||||||
|
switch engine {
|
||||||
|
case "sqlite3", "sqlite":
|
||||||
|
return sqlite.InitDB(r.db)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported db")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repo) GetLicenses(ctx context.Context) ([]model.License, error) {
|
||||||
|
licenses := []model.License{}
|
||||||
|
|
||||||
|
query := "SELECT key, activationId, planDetails, validationMessage FROM licenses"
|
||||||
|
|
||||||
|
err := r.db.Select(&licenses, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get licenses from db: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return licenses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetActiveLicense fetches the latest active license from DB
|
||||||
|
func (r *Repo) GetActiveLicense(ctx context.Context) (*model.License, error) {
|
||||||
|
var err error
|
||||||
|
licenses := []model.License{}
|
||||||
|
|
||||||
|
query := "SELECT key, activationId, planDetails, validationMessage FROM licenses"
|
||||||
|
|
||||||
|
err = r.db.Select(&licenses, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get active licenses from db: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var active *model.License
|
||||||
|
for _, l := range licenses {
|
||||||
|
l.ParsePlan()
|
||||||
|
if active == nil &&
|
||||||
|
(l.ValidFrom != 0) &&
|
||||||
|
(l.ValidUntil == -1 || l.ValidUntil > time.Now().Unix()) {
|
||||||
|
active = &l
|
||||||
|
}
|
||||||
|
if active != nil &&
|
||||||
|
l.ValidFrom > active.ValidFrom &&
|
||||||
|
(l.ValidUntil == -1 || l.ValidUntil > time.Now().Unix()) {
|
||||||
|
active = &l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return active, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertLicense inserts a new license in db
|
||||||
|
func (r *Repo) InsertLicense(ctx context.Context, l *model.License) error {
|
||||||
|
|
||||||
|
if l.Key == "" {
|
||||||
|
return fmt.Errorf("insert license failed: license key is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
query := `INSERT INTO licenses
|
||||||
|
(key, planDetails, activationId, validationmessage)
|
||||||
|
VALUES ($1, $2, $3, $4)`
|
||||||
|
|
||||||
|
_, err := r.db.ExecContext(ctx,
|
||||||
|
query,
|
||||||
|
l.Key,
|
||||||
|
l.PlanDetails,
|
||||||
|
l.ActivationId,
|
||||||
|
l.ValidationMessage)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf("error in inserting license data: ", zap.Error(err))
|
||||||
|
return fmt.Errorf("failed to insert license in db: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePlanDetails writes new plan details to the db
|
||||||
|
func (r *Repo) UpdatePlanDetails(ctx context.Context,
|
||||||
|
key,
|
||||||
|
planDetails string) error {
|
||||||
|
|
||||||
|
if key == "" {
|
||||||
|
return fmt.Errorf("Update Plan Details failed: license key is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
query := `UPDATE licenses
|
||||||
|
SET planDetails = $1,
|
||||||
|
updatedAt = $2
|
||||||
|
WHERE key = $3`
|
||||||
|
|
||||||
|
_, err := r.db.ExecContext(ctx, query, planDetails, time.Now(), key)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf("error in updating license: ", zap.Error(err))
|
||||||
|
return fmt.Errorf("failed to update license in db: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
295
ee/query-service/license/manager.go
Normal file
295
ee/query-service/license/manager.go
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
package license
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
validate "go.signoz.io/signoz/ee/query-service/integrations/signozio"
|
||||||
|
"go.signoz.io/signoz/ee/query-service/model"
|
||||||
|
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/telemetry"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
var LM *Manager
|
||||||
|
|
||||||
|
// validate and update license every 24 hours
|
||||||
|
var validationFrequency = 24 * 60 * time.Minute
|
||||||
|
|
||||||
|
type Manager struct {
|
||||||
|
repo *Repo
|
||||||
|
mutex sync.Mutex
|
||||||
|
|
||||||
|
validatorRunning bool
|
||||||
|
|
||||||
|
// end the license validation, this is important to gracefully
|
||||||
|
// stopping validation and protect in-consistent updates
|
||||||
|
done chan struct{}
|
||||||
|
|
||||||
|
// terminated waits for the validate go routine to end
|
||||||
|
terminated chan struct{}
|
||||||
|
|
||||||
|
// last time the license was validated
|
||||||
|
lastValidated int64
|
||||||
|
|
||||||
|
// keep track of validation failure attempts
|
||||||
|
failedAttempts uint64
|
||||||
|
|
||||||
|
// keep track of active license and features
|
||||||
|
activeLicense *model.License
|
||||||
|
activeFeatures basemodel.FeatureSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartManager(dbType string, db *sqlx.DB) (*Manager, error) {
|
||||||
|
|
||||||
|
if LM != nil {
|
||||||
|
return LM, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
repo := NewLicenseRepo(db)
|
||||||
|
err := repo.InitDB(dbType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to initiate license repo: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m := &Manager{
|
||||||
|
repo: &repo,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.start(); err != nil {
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
LM = m
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// start loads active license in memory and initiates validator
|
||||||
|
func (lm *Manager) start() error {
|
||||||
|
err := lm.LoadActiveLicense()
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lm *Manager) Stop() {
|
||||||
|
close(lm.done)
|
||||||
|
<-lm.terminated
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lm *Manager) SetActive(l *model.License) {
|
||||||
|
lm.mutex.Lock()
|
||||||
|
defer lm.mutex.Unlock()
|
||||||
|
|
||||||
|
if l == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lm.activeLicense = l
|
||||||
|
lm.activeFeatures = l.FeatureSet
|
||||||
|
if !lm.validatorRunning {
|
||||||
|
// we want to make sure only one validator runs,
|
||||||
|
// we already have lock() so good to go
|
||||||
|
lm.validatorRunning = true
|
||||||
|
go lm.Validator(context.Background())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadActiveLicense loads the most recent active licenseex
|
||||||
|
func (lm *Manager) LoadActiveLicense() error {
|
||||||
|
var err error
|
||||||
|
active, err := lm.repo.GetActiveLicense(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if active != nil {
|
||||||
|
lm.SetActive(active)
|
||||||
|
} else {
|
||||||
|
zap.S().Info("No active license found.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lm *Manager) GetLicenses(ctx context.Context) (response []model.License, apiError *model.ApiError) {
|
||||||
|
|
||||||
|
licenses, err := lm.repo.GetLicenses(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.InternalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, l := range licenses {
|
||||||
|
l.ParsePlan()
|
||||||
|
|
||||||
|
if l.Key == lm.activeLicense.Key {
|
||||||
|
l.IsCurrent = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.ValidUntil == -1 {
|
||||||
|
// for subscriptions, there is no end-date as such
|
||||||
|
// but for showing user some validity we default one year timespan
|
||||||
|
l.ValidUntil = l.ValidFrom + 31556926
|
||||||
|
}
|
||||||
|
|
||||||
|
response = append(response, l)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validator validates license after an epoch of time
|
||||||
|
func (lm *Manager) Validator(ctx context.Context) {
|
||||||
|
defer close(lm.terminated)
|
||||||
|
tick := time.NewTicker(validationFrequency)
|
||||||
|
defer tick.Stop()
|
||||||
|
|
||||||
|
lm.Validate(ctx)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-lm.done:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
select {
|
||||||
|
case <-lm.done:
|
||||||
|
return
|
||||||
|
case <-tick.C:
|
||||||
|
lm.Validate(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates the current active license
|
||||||
|
func (lm *Manager) Validate(ctx context.Context) (reterr error) {
|
||||||
|
zap.S().Info("License validation started")
|
||||||
|
if lm.activeLicense == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
lm.mutex.Lock()
|
||||||
|
|
||||||
|
lm.lastValidated = time.Now().Unix()
|
||||||
|
if reterr != nil {
|
||||||
|
zap.S().Errorf("License validation completed with error", reterr)
|
||||||
|
atomic.AddUint64(&lm.failedAttempts, 1)
|
||||||
|
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_LICENSE_CHECK_FAILED,
|
||||||
|
map[string]interface{}{"err": reterr.Error()})
|
||||||
|
} else {
|
||||||
|
zap.S().Info("License validation completed with no errors")
|
||||||
|
}
|
||||||
|
|
||||||
|
lm.mutex.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
response, apiError := validate.ValidateLicense(lm.activeLicense.ActivationId)
|
||||||
|
if apiError != nil {
|
||||||
|
zap.S().Errorf("failed to validate license", apiError)
|
||||||
|
return apiError.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.PlanDetails == lm.activeLicense.PlanDetails {
|
||||||
|
// license plan hasnt changed, nothing to do
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.PlanDetails != "" {
|
||||||
|
|
||||||
|
// copy and replace the active license record
|
||||||
|
l := model.License{
|
||||||
|
Key: lm.activeLicense.Key,
|
||||||
|
CreatedAt: lm.activeLicense.CreatedAt,
|
||||||
|
PlanDetails: response.PlanDetails,
|
||||||
|
ValidationMessage: lm.activeLicense.ValidationMessage,
|
||||||
|
ActivationId: lm.activeLicense.ActivationId,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := l.ParsePlan(); err != nil {
|
||||||
|
zap.S().Errorf("failed to parse updated license", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// updated plan is parsable, check if plan has changed
|
||||||
|
if lm.activeLicense.PlanDetails != response.PlanDetails {
|
||||||
|
err := lm.repo.UpdatePlanDetails(ctx, lm.activeLicense.Key, response.PlanDetails)
|
||||||
|
if err != nil {
|
||||||
|
// unexpected db write issue but we can let the user continue
|
||||||
|
// and wait for update to work in next cycle.
|
||||||
|
zap.S().Errorf("failed to validate license", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// activate the update license plan
|
||||||
|
lm.SetActive(&l)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activate activates a license key with signoz server
|
||||||
|
func (lm *Manager) Activate(ctx context.Context, key string) (licenseResponse *model.License, errResponse *model.ApiError) {
|
||||||
|
defer func() {
|
||||||
|
if errResponse != nil {
|
||||||
|
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_LICENSE_ACT_FAILED,
|
||||||
|
map[string]interface{}{"err": errResponse.Err.Error()})
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
response, apiError := validate.ActivateLicense(key, "")
|
||||||
|
if apiError != nil {
|
||||||
|
zap.S().Errorf("failed to activate license", zap.Error(apiError.Err))
|
||||||
|
return nil, apiError
|
||||||
|
}
|
||||||
|
|
||||||
|
l := &model.License{
|
||||||
|
Key: key,
|
||||||
|
ActivationId: response.ActivationId,
|
||||||
|
PlanDetails: response.PlanDetails,
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse validity and features from the plan details
|
||||||
|
err := l.ParsePlan()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf("failed to activate license", zap.Error(err))
|
||||||
|
return nil, model.InternalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// store the license before activating it
|
||||||
|
err = lm.repo.InsertLicense(ctx, l)
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf("failed to activate license", zap.Error(err))
|
||||||
|
return nil, model.InternalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// license is valid, activate it
|
||||||
|
lm.SetActive(l)
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckFeature will be internally used by backend routines
|
||||||
|
// for feature gating
|
||||||
|
func (lm *Manager) CheckFeature(featureKey string) error {
|
||||||
|
if _, ok := lm.activeFeatures[featureKey]; ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return basemodel.ErrFeatureUnavailable{Key: featureKey}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFeatureFlags returns current active features
|
||||||
|
func (lm *Manager) GetFeatureFlags() basemodel.FeatureSet {
|
||||||
|
return lm.activeFeatures
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRepo return the license repo
|
||||||
|
func (lm *Manager) GetRepo() *Repo {
|
||||||
|
return lm.repo
|
||||||
|
}
|
37
ee/query-service/license/sqlite/init.go
Normal file
37
ee/query-service/license/sqlite/init.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func InitDB(db *sqlx.DB) error {
|
||||||
|
var err error
|
||||||
|
if db == nil {
|
||||||
|
return fmt.Errorf("invalid db connection")
|
||||||
|
}
|
||||||
|
|
||||||
|
table_schema := `CREATE TABLE IF NOT EXISTS licenses(
|
||||||
|
key TEXT PRIMARY KEY,
|
||||||
|
createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
planDetails TEXT,
|
||||||
|
activationId TEXT,
|
||||||
|
validationMessage TEXT,
|
||||||
|
lastValidated TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS sites(
|
||||||
|
uuid TEXT PRIMARY KEY,
|
||||||
|
alias VARCHAR(180) DEFAULT 'PROD',
|
||||||
|
url VARCHAR(300),
|
||||||
|
createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
`
|
||||||
|
|
||||||
|
_, err = db.Exec(table_schema)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error in creating licenses table: %s", err.Error())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
90
ee/query-service/main.go
Normal file
90
ee/query-service/main.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"go.signoz.io/signoz/ee/query-service/app"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/auth"
|
||||||
|
baseconst "go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/version"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
func initZapLog() *zap.Logger {
|
||||||
|
config := zap.NewDevelopmentConfig()
|
||||||
|
config.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
|
||||||
|
config.EncoderConfig.TimeKey = "timestamp"
|
||||||
|
config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||||
|
logger, _ := config.Build()
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var promConfigPath string
|
||||||
|
|
||||||
|
// disables rule execution but allows change to the rule definition
|
||||||
|
var disableRules bool
|
||||||
|
|
||||||
|
// the url used to build link in the alert messages in slack and other systems
|
||||||
|
var ruleRepoURL string
|
||||||
|
|
||||||
|
flag.StringVar(&promConfigPath, "config", "./config/prometheus.yml", "(prometheus config to read metrics)")
|
||||||
|
flag.BoolVar(&disableRules, "rules.disable", false, "(disable rule evaluation)")
|
||||||
|
flag.StringVar(&ruleRepoURL, "rules.repo-url", baseconst.AlertHelpPage, "(host address used to build rule link in alert messages)")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
loggerMgr := initZapLog()
|
||||||
|
zap.ReplaceGlobals(loggerMgr)
|
||||||
|
defer loggerMgr.Sync() // flushes buffer, if any
|
||||||
|
|
||||||
|
logger := loggerMgr.Sugar()
|
||||||
|
version.PrintVersion()
|
||||||
|
|
||||||
|
serverOptions := &app.ServerOptions{
|
||||||
|
HTTPHostPort: baseconst.HTTPHostPort,
|
||||||
|
PromConfigPath: promConfigPath,
|
||||||
|
PrivateHostPort: baseconst.PrivateHostPort,
|
||||||
|
DisableRules: disableRules,
|
||||||
|
RuleRepoURL: ruleRepoURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the jwt secret key
|
||||||
|
auth.JwtSecret = os.Getenv("SIGNOZ_JWT_SECRET")
|
||||||
|
|
||||||
|
if len(auth.JwtSecret) == 0 {
|
||||||
|
zap.S().Warn("No JWT secret key is specified.")
|
||||||
|
} else {
|
||||||
|
zap.S().Info("No JWT secret key set successfully.")
|
||||||
|
}
|
||||||
|
|
||||||
|
server, err := app.NewServer(serverOptions)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal("Failed to create server", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := server.Start(); err != nil {
|
||||||
|
logger.Fatal("Could not start servers", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := auth.InitAuthCache(context.Background()); err != nil {
|
||||||
|
logger.Fatal("Failed to initialize auth cache", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
signalsChannel := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(signalsChannel, os.Interrupt, syscall.SIGTERM)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case status := <-server.HealthCheckStatus():
|
||||||
|
logger.Info("Received HealthCheck status: ", zap.Int("status", int(status)))
|
||||||
|
case <-signalsChannel:
|
||||||
|
logger.Fatal("Received OS Interrupt Signal ... ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
ee/query-service/model/auth.go
Normal file
21
ee/query-service/model/auth.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PrecheckResponse contains login precheck response
|
||||||
|
type PrecheckResponse struct {
|
||||||
|
SSO bool `json:"sso"`
|
||||||
|
SsoUrl string `json:"ssoUrl"`
|
||||||
|
CanSelfRegister bool `json:"canSelfRegister"`
|
||||||
|
IsUser bool `json:"isUser"`
|
||||||
|
SsoError string `json:"ssoError"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GettableInvitation overrides base object and adds precheck into
|
||||||
|
// response
|
||||||
|
type GettableInvitation struct {
|
||||||
|
*basemodel.InvitationResponseObject
|
||||||
|
Precheck *PrecheckResponse `json:"precheck"`
|
||||||
|
}
|
142
ee/query-service/model/domain.go
Normal file
142
ee/query-service/model/domain.go
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
saml2 "github.com/russellhaering/gosaml2"
|
||||||
|
"go.signoz.io/signoz/ee/query-service/saml"
|
||||||
|
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SSOType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
SAML SSOType = "SAML"
|
||||||
|
GoogleAuth SSOType = "GOOGLE_AUTH"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SamlConfig struct {
|
||||||
|
SamlEntity string `json:"samlEntity"`
|
||||||
|
SamlIdp string `json:"samlIdp"`
|
||||||
|
SamlCert string `json:"samlCert"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrgDomain identify org owned web domains for auth and other purposes
|
||||||
|
type OrgDomain struct {
|
||||||
|
Id uuid.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
OrgId string `json:"orgId"`
|
||||||
|
SsoEnabled bool `json:"ssoEnabled"`
|
||||||
|
SsoType SSOType `json:"ssoType"`
|
||||||
|
SamlConfig *SamlConfig `json:"samlConfig"`
|
||||||
|
Org *basemodel.Organization
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid is used a pipeline function to check if org domain
|
||||||
|
// loaded from db is valid
|
||||||
|
func (od *OrgDomain) Valid(err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if od.Id == uuid.Nil || od.OrgId == "" {
|
||||||
|
return fmt.Errorf("both id and orgId are required")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidNew cheks if the org domain is valid for insertion in db
|
||||||
|
func (od *OrgDomain) ValidNew() error {
|
||||||
|
|
||||||
|
if od.OrgId == "" {
|
||||||
|
return fmt.Errorf("orgId is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if od.Name == "" {
|
||||||
|
return fmt.Errorf("name is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadConfig loads config params from json text
|
||||||
|
func (od *OrgDomain) LoadConfig(jsondata string) error {
|
||||||
|
d := *od
|
||||||
|
err := json.Unmarshal([]byte(jsondata), &d)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to marshal json to OrgDomain{}")
|
||||||
|
}
|
||||||
|
*od = d
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (od *OrgDomain) GetSAMLEntityID() string {
|
||||||
|
if od.SamlConfig != nil {
|
||||||
|
return od.SamlConfig.SamlEntity
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (od *OrgDomain) GetSAMLIdpURL() string {
|
||||||
|
if od.SamlConfig != nil {
|
||||||
|
return od.SamlConfig.SamlIdp
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (od *OrgDomain) GetSAMLCert() string {
|
||||||
|
if od.SamlConfig != nil {
|
||||||
|
return od.SamlConfig.SamlCert
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrepareSamlRequest creates a request accordingly gosaml2
|
||||||
|
func (od *OrgDomain) PrepareSamlRequest(siteUrl *url.URL) (*saml2.SAMLServiceProvider, error) {
|
||||||
|
|
||||||
|
// this is the url Idp will call after login completion
|
||||||
|
acs := fmt.Sprintf("%s://%s/%s",
|
||||||
|
siteUrl.Scheme,
|
||||||
|
siteUrl.Host,
|
||||||
|
"api/v1/complete/saml")
|
||||||
|
|
||||||
|
// this is the address of the calling url, useful to redirect user
|
||||||
|
sourceUrl := fmt.Sprintf("%s://%s%s",
|
||||||
|
siteUrl.Scheme,
|
||||||
|
siteUrl.Host,
|
||||||
|
siteUrl.Path)
|
||||||
|
|
||||||
|
// ideally this should be some unique ID for each installation
|
||||||
|
// but since we dont have UI to support it, we default it to
|
||||||
|
// host. this issuer is an identifier of service provider (signoz)
|
||||||
|
// on id provider (e.g. azure, okta). Azure requires this id to be configured
|
||||||
|
// in their system, while others seem to not care about it.
|
||||||
|
// currently we default it to host from window.location (received from browser)
|
||||||
|
issuer := siteUrl.Host
|
||||||
|
|
||||||
|
return saml.PrepareRequest(issuer, acs, sourceUrl, od.GetSAMLEntityID(), od.GetSAMLIdpURL(), od.GetSAMLCert())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (od *OrgDomain) BuildSsoUrl(siteUrl *url.URL) (ssoUrl string, err error) {
|
||||||
|
|
||||||
|
sp, err := od.PrepareSamlRequest(siteUrl)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmtDomainId := strings.Replace(od.Id.String(), "-", ":", -1)
|
||||||
|
|
||||||
|
relayState := fmt.Sprintf("%s://%s%s?domainId=%s",
|
||||||
|
siteUrl.Scheme,
|
||||||
|
siteUrl.Host,
|
||||||
|
siteUrl.Path,
|
||||||
|
fmtDomainId)
|
||||||
|
|
||||||
|
return sp.BuildAuthURL(relayState)
|
||||||
|
}
|
91
ee/query-service/model/errors.go
Normal file
91
ee/query-service/model/errors.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ApiError struct {
|
||||||
|
Typ basemodel.ErrorType
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiError) Type() basemodel.ErrorType {
|
||||||
|
return a.Typ
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiError) ToError() error {
|
||||||
|
if a != nil {
|
||||||
|
return a.Err
|
||||||
|
}
|
||||||
|
return a.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiError) Error() string {
|
||||||
|
return a.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiError) IsNil() bool {
|
||||||
|
return a == nil || a.Err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewApiError returns a ApiError object of given type
|
||||||
|
func NewApiError(typ basemodel.ErrorType, err error) *ApiError {
|
||||||
|
return &ApiError{
|
||||||
|
Typ: typ,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BadRequest returns a ApiError object of bad request
|
||||||
|
func BadRequest(err error) *ApiError {
|
||||||
|
return &ApiError{
|
||||||
|
Typ: basemodel.ErrorBadData,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InternalError returns a ApiError object of internal type
|
||||||
|
func InternalError(err error) *ApiError {
|
||||||
|
return &ApiError{
|
||||||
|
Typ: basemodel.ErrorInternal,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrorNone basemodel.ErrorType = ""
|
||||||
|
ErrorTimeout basemodel.ErrorType = "timeout"
|
||||||
|
ErrorCanceled basemodel.ErrorType = "canceled"
|
||||||
|
ErrorExec basemodel.ErrorType = "execution"
|
||||||
|
ErrorBadData basemodel.ErrorType = "bad_data"
|
||||||
|
ErrorInternal basemodel.ErrorType = "internal"
|
||||||
|
ErrorUnavailable basemodel.ErrorType = "unavailable"
|
||||||
|
ErrorNotFound basemodel.ErrorType = "not_found"
|
||||||
|
ErrorNotImplemented basemodel.ErrorType = "not_implemented"
|
||||||
|
ErrorUnauthorized basemodel.ErrorType = "unauthorized"
|
||||||
|
ErrorForbidden basemodel.ErrorType = "forbidden"
|
||||||
|
ErrorConflict basemodel.ErrorType = "conflict"
|
||||||
|
ErrorStreamingNotSupported basemodel.ErrorType = "streaming is not supported"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ErrorNone = basemodel.ErrorNone
|
||||||
|
ErrorTimeout = basemodel.ErrorTimeout
|
||||||
|
ErrorCanceled = basemodel.ErrorCanceled
|
||||||
|
ErrorExec = basemodel.ErrorExec
|
||||||
|
ErrorBadData = basemodel.ErrorBadData
|
||||||
|
ErrorInternal = basemodel.ErrorInternal
|
||||||
|
ErrorUnavailable = basemodel.ErrorUnavailable
|
||||||
|
ErrorNotFound = basemodel.ErrorNotFound
|
||||||
|
ErrorNotImplemented = basemodel.ErrorNotImplemented
|
||||||
|
ErrorUnauthorized = basemodel.ErrorUnauthorized
|
||||||
|
ErrorForbidden = basemodel.ErrorForbidden
|
||||||
|
ErrorConflict = basemodel.ErrorConflict
|
||||||
|
ErrorStreamingNotSupported = basemodel.ErrorStreamingNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrUnsupportedAuth struct{}
|
||||||
|
|
||||||
|
func (errUnsupportedAuth ErrUnsupportedAuth) Error() string {
|
||||||
|
return "this authentication method not supported"
|
||||||
|
}
|
91
ee/query-service/model/license.go
Normal file
91
ee/query-service/model/license.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type License struct {
|
||||||
|
Key string `json:"key" db:"key"`
|
||||||
|
ActivationId string `json:"activationId" db:"activationId"`
|
||||||
|
CreatedAt time.Time `db:"created_at"`
|
||||||
|
|
||||||
|
// PlanDetails contains the encrypted plan info
|
||||||
|
PlanDetails string `json:"planDetails" db:"planDetails"`
|
||||||
|
|
||||||
|
// stores parsed license details
|
||||||
|
LicensePlan
|
||||||
|
|
||||||
|
FeatureSet basemodel.FeatureSet
|
||||||
|
|
||||||
|
// populated in case license has any errors
|
||||||
|
ValidationMessage string `db:"validationMessage"`
|
||||||
|
|
||||||
|
// used only for sending details to front-end
|
||||||
|
IsCurrent bool `json:"isCurrent"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *License) MarshalJSON() ([]byte, error) {
|
||||||
|
|
||||||
|
return json.Marshal(&struct {
|
||||||
|
Key string `json:"key" db:"key"`
|
||||||
|
ActivationId string `json:"activationId" db:"activationId"`
|
||||||
|
ValidationMessage string `db:"validationMessage"`
|
||||||
|
IsCurrent bool `json:"isCurrent"`
|
||||||
|
PlanKey string `json:"planKey"`
|
||||||
|
ValidFrom time.Time `json:"ValidFrom"`
|
||||||
|
ValidUntil time.Time `json:"ValidUntil"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
}{
|
||||||
|
Key: l.Key,
|
||||||
|
ActivationId: l.ActivationId,
|
||||||
|
IsCurrent: l.IsCurrent,
|
||||||
|
PlanKey: l.PlanKey,
|
||||||
|
ValidFrom: time.Unix(l.ValidFrom, 0),
|
||||||
|
ValidUntil: time.Unix(l.ValidUntil, 0),
|
||||||
|
Status: l.Status,
|
||||||
|
ValidationMessage: l.ValidationMessage,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type LicensePlan struct {
|
||||||
|
PlanKey string `json:"planKey"`
|
||||||
|
ValidFrom int64 `json:"validFrom"`
|
||||||
|
ValidUntil int64 `json:"validUntil"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *License) ParsePlan() error {
|
||||||
|
l.LicensePlan = LicensePlan{}
|
||||||
|
|
||||||
|
planData, err := base64.StdEncoding.DecodeString(l.PlanDetails)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
plan := LicensePlan{}
|
||||||
|
err = json.Unmarshal([]byte(planData), &plan)
|
||||||
|
if err != nil {
|
||||||
|
l.ValidationMessage = "failed to parse plan from license"
|
||||||
|
return errors.Wrap(err, "failed to parse plan from license")
|
||||||
|
}
|
||||||
|
|
||||||
|
l.LicensePlan = plan
|
||||||
|
l.ParseFeatures()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *License) ParseFeatures() {
|
||||||
|
switch l.PlanKey {
|
||||||
|
case Pro:
|
||||||
|
l.FeatureSet = ProPlan
|
||||||
|
case Enterprise:
|
||||||
|
l.FeatureSet = EnterprisePlan
|
||||||
|
default:
|
||||||
|
l.FeatureSet = BasicPlan
|
||||||
|
}
|
||||||
|
}
|
27
ee/query-service/model/plans.go
Normal file
27
ee/query-service/model/plans.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
const SSO = "SSO"
|
||||||
|
const Basic = "BASIC_PLAN"
|
||||||
|
const Pro = "PRO_PLAN"
|
||||||
|
const Enterprise = "ENTERPRISE_PLAN"
|
||||||
|
const DisableUpsell = "DISABLE_UPSELL"
|
||||||
|
|
||||||
|
var BasicPlan = basemodel.FeatureSet{
|
||||||
|
Basic: true,
|
||||||
|
SSO: false,
|
||||||
|
DisableUpsell: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
var ProPlan = basemodel.FeatureSet{
|
||||||
|
Pro: true,
|
||||||
|
SSO: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
var EnterprisePlan = basemodel.FeatureSet{
|
||||||
|
Enterprise: true,
|
||||||
|
SSO: true,
|
||||||
|
}
|
35
ee/query-service/model/usage.go
Normal file
35
ee/query-service/model/usage.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UsageSnapshot struct {
|
||||||
|
CurrentLogSizeBytes uint64 `json:"currentLogSizeBytes"`
|
||||||
|
CurrentLogSizeBytesColdStorage uint64 `json:"currentLogSizeBytesColdStorage"`
|
||||||
|
CurrentSpansCount uint64 `json:"currentSpansCount"`
|
||||||
|
CurrentSpansCountColdStorage uint64 `json:"currentSpansCountColdStorage"`
|
||||||
|
CurrentSamplesCount uint64 `json:"currentSamplesCount"`
|
||||||
|
CurrentSamplesCountColdStorage uint64 `json:"currentSamplesCountColdStorage"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UsageBase struct {
|
||||||
|
Id uuid.UUID `json:"id" db:"id"`
|
||||||
|
InstallationId uuid.UUID `json:"installationId" db:"installation_id"`
|
||||||
|
ActivationId uuid.UUID `json:"activationId" db:"activation_id"`
|
||||||
|
CreatedAt time.Time `json:"createdAt" db:"created_at"`
|
||||||
|
FailedSyncRequest int `json:"failedSyncRequest" db:"failed_sync_request_count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UsagePayload struct {
|
||||||
|
UsageBase
|
||||||
|
Metrics UsageSnapshot `json:"metrics"`
|
||||||
|
SnapshotDate time.Time `json:"snapshotDate"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Usage struct {
|
||||||
|
UsageBase
|
||||||
|
Snapshot string `db:"snapshot"`
|
||||||
|
}
|
107
ee/query-service/saml/request.go
Normal file
107
ee/query-service/saml/request.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
package saml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
saml2 "github.com/russellhaering/gosaml2"
|
||||||
|
dsig "github.com/russellhaering/goxmldsig"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LoadCertificateStore(certString string) (dsig.X509CertificateStore, error) {
|
||||||
|
certStore := &dsig.MemoryX509CertificateStore{
|
||||||
|
Roots: []*x509.Certificate{},
|
||||||
|
}
|
||||||
|
|
||||||
|
certData, err := base64.StdEncoding.DecodeString(certString)
|
||||||
|
if err != nil {
|
||||||
|
return certStore, fmt.Errorf(fmt.Sprintf("failed to read certificate: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
idpCert, err := x509.ParseCertificate(certData)
|
||||||
|
if err != nil {
|
||||||
|
return certStore, fmt.Errorf(fmt.Sprintf("failed to prepare saml request, invalid cert: %s", err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
|
certStore.Roots = append(certStore.Roots, idpCert)
|
||||||
|
|
||||||
|
return certStore, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadCertFromPem(certString string) (dsig.X509CertificateStore, error) {
|
||||||
|
certStore := &dsig.MemoryX509CertificateStore{
|
||||||
|
Roots: []*x509.Certificate{},
|
||||||
|
}
|
||||||
|
|
||||||
|
block, _ := pem.Decode([]byte(certString))
|
||||||
|
if block == nil {
|
||||||
|
return certStore, fmt.Errorf("no valid pem cert found")
|
||||||
|
}
|
||||||
|
|
||||||
|
idpCert, err := x509.ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return certStore, fmt.Errorf(fmt.Sprintf("failed to parse pem cert: %s", err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
|
certStore.Roots = append(certStore.Roots, idpCert)
|
||||||
|
|
||||||
|
return certStore, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrepareRequest prepares authorization URL (Idp Provider URL)
|
||||||
|
func PrepareRequest(issuer, acsUrl, audience, entity, idp, certString string) (*saml2.SAMLServiceProvider, error) {
|
||||||
|
var certStore dsig.X509CertificateStore
|
||||||
|
if certString == "" {
|
||||||
|
return nil, fmt.Errorf("invalid certificate data")
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if strings.Contains(certString, "-----BEGIN CERTIFICATE-----") {
|
||||||
|
certStore, err = LoadCertFromPem(certString)
|
||||||
|
} else {
|
||||||
|
certStore, err = LoadCertificateStore(certString)
|
||||||
|
}
|
||||||
|
// certificate store can not be created, throw error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
randomKeyStore := dsig.RandomKeyStoreForTest()
|
||||||
|
|
||||||
|
// SIGNOZ_SAML_RETURN_URL env var would support overriding window.location
|
||||||
|
// as return destination after saml request is complete from IdP side.
|
||||||
|
// this var is also useful for development, as it is easy to override with backend endpoint
|
||||||
|
// e.g. http://localhost:8080/api/v1/complete/saml
|
||||||
|
acsUrl = constants.GetOrDefaultEnv("SIGNOZ_SAML_RETURN_URL", acsUrl)
|
||||||
|
|
||||||
|
sp := &saml2.SAMLServiceProvider{
|
||||||
|
IdentityProviderSSOURL: idp,
|
||||||
|
IdentityProviderIssuer: entity,
|
||||||
|
ServiceProviderIssuer: issuer,
|
||||||
|
AssertionConsumerServiceURL: acsUrl,
|
||||||
|
SignAuthnRequests: true,
|
||||||
|
AllowMissingAttributes: true,
|
||||||
|
|
||||||
|
// about cert stores -sender(signoz app) and receiver (idp)
|
||||||
|
// The random key (random key store) is sender cert. The public cert store(IDPCertificateStore) that you see on org domain is receiver cert (idp provided).
|
||||||
|
// At the moment, the library we use doesn't bother about sender cert and IdP too. It just adds additional layer of security, which we can explore in future versions
|
||||||
|
// The receiver (Idp) cert will be different for each org domain. Imagine cloud setup where each company setups their domain that integrates with their Idp.
|
||||||
|
// @signoz.io
|
||||||
|
// @next.io
|
||||||
|
// Each of above will have their own Idp setup and hence separate public cert to decrypt the response.
|
||||||
|
// The way SAML request travels is -
|
||||||
|
// SigNoz Backend -> IdP Login Screen -> SigNoz Backend -> SigNoz Frontend
|
||||||
|
// ---------------- | -------------------| -------------------------------------
|
||||||
|
// The dotted lines indicate request boundries. So if you notice, the response from Idp starts a new request. hence we need relay state to pass the context around.
|
||||||
|
|
||||||
|
IDPCertificateStore: certStore,
|
||||||
|
SPKeyStore: randomKeyStore,
|
||||||
|
}
|
||||||
|
zap.S().Debugf("SAML request:", sp)
|
||||||
|
return sp, nil
|
||||||
|
}
|
321
ee/query-service/usage/manager.go
Normal file
321
ee/query-service/usage/manager.go
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
package usage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ClickHouse/clickhouse-go/v2"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
licenseserver "go.signoz.io/signoz/ee/query-service/integrations/signozio"
|
||||||
|
"go.signoz.io/signoz/ee/query-service/license"
|
||||||
|
"go.signoz.io/signoz/ee/query-service/model"
|
||||||
|
"go.signoz.io/signoz/ee/query-service/usage/repository"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/utils/encryption"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MaxRetries = 3
|
||||||
|
RetryInterval = 5 * time.Second
|
||||||
|
stateUnlocked uint32 = 0
|
||||||
|
stateLocked uint32 = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// // collect usage every hour
|
||||||
|
// collectionFrequency = 1 * time.Hour
|
||||||
|
|
||||||
|
// // send usage every 24 hour
|
||||||
|
// uploadFrequency = 24 * time.Hour
|
||||||
|
|
||||||
|
// collect usage every hour
|
||||||
|
collectionFrequency = 5 * time.Second
|
||||||
|
|
||||||
|
// send usage every 24 hour
|
||||||
|
uploadFrequency = 30 * time.Second
|
||||||
|
|
||||||
|
locker = stateUnlocked
|
||||||
|
)
|
||||||
|
|
||||||
|
type Manager struct {
|
||||||
|
repository *repository.Repository
|
||||||
|
|
||||||
|
clickhouseConn clickhouse.Conn
|
||||||
|
|
||||||
|
licenseRepo *license.Repo
|
||||||
|
|
||||||
|
// end the usage routine, this is important to gracefully
|
||||||
|
// stopping usage reporting and protect in-consistent updates
|
||||||
|
done chan struct{}
|
||||||
|
|
||||||
|
// terminated waits for the UsageExporter go routine to end
|
||||||
|
terminated chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(dbType string, db *sqlx.DB, licenseRepo *license.Repo, clickhouseConn clickhouse.Conn) (*Manager, error) {
|
||||||
|
repo := repository.New(db)
|
||||||
|
|
||||||
|
err := repo.Init(dbType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to initiate usage repo: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m := &Manager{
|
||||||
|
repository: repo,
|
||||||
|
clickhouseConn: clickhouseConn,
|
||||||
|
licenseRepo: licenseRepo,
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// start loads collects and exports any exported snapshot and starts the exporter
|
||||||
|
func (lm *Manager) Start() error {
|
||||||
|
// compares the locker and stateUnlocked if both are same lock is applied else returns error
|
||||||
|
if !atomic.CompareAndSwapUint32(&locker, stateUnlocked, stateLocked) {
|
||||||
|
return fmt.Errorf("usage exporter is locked")
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if license is present or not
|
||||||
|
license, err := lm.licenseRepo.GetActiveLicense(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get active license")
|
||||||
|
}
|
||||||
|
if license == nil {
|
||||||
|
// we will not start the usage reporting if license is not present.
|
||||||
|
zap.S().Info("no license present, skipping usage reporting")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// upload previous snapshots if any
|
||||||
|
err = lm.UploadUsage(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// collect snapshot if incase it wasn't collect in (t - collectionFrequency)
|
||||||
|
err = lm.CollectCurrentUsage(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
go lm.UsageExporter(context.Background())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CollectCurrentUsage checks if needs to collect usage data
|
||||||
|
func (lm *Manager) CollectCurrentUsage(ctx context.Context) error {
|
||||||
|
// check the DB if anything exist where timestamp > t - collectionFrequency
|
||||||
|
ts := time.Now().Add(-collectionFrequency)
|
||||||
|
alreadyCreated, err := lm.repository.CheckSnapshotGtCreatedAt(ctx, ts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !alreadyCreated {
|
||||||
|
zap.S().Info("Collecting current usage")
|
||||||
|
exportError := lm.CollectAndStoreUsage(ctx)
|
||||||
|
if exportError != nil {
|
||||||
|
return exportError
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
zap.S().Info("Nothing to collect")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lm *Manager) UsageExporter(ctx context.Context) {
|
||||||
|
defer close(lm.terminated)
|
||||||
|
|
||||||
|
collectionTicker := time.NewTicker(collectionFrequency)
|
||||||
|
defer collectionTicker.Stop()
|
||||||
|
|
||||||
|
uploadTicker := time.NewTicker(uploadFrequency)
|
||||||
|
defer uploadTicker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-lm.done:
|
||||||
|
return
|
||||||
|
case <-collectionTicker.C:
|
||||||
|
lm.CollectAndStoreUsage(ctx)
|
||||||
|
case <-uploadTicker.C:
|
||||||
|
lm.UploadUsage(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TableSize struct {
|
||||||
|
Table string `ch:"table"`
|
||||||
|
DiskName string `ch:"disk_name"`
|
||||||
|
Rows uint64 `ch:"rows"`
|
||||||
|
UncompressedBytes uint64 `ch:"uncompressed_bytes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lm *Manager) CollectAndStoreUsage(ctx context.Context) error {
|
||||||
|
snap, err := lm.GetUsageFromClickHouse(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
license, err := lm.licenseRepo.GetActiveLicense(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
activationId, _ := uuid.Parse(license.ActivationId)
|
||||||
|
// TODO (nitya) : Add installation ID in the payload
|
||||||
|
payload := model.UsagePayload{
|
||||||
|
UsageBase: model.UsageBase{
|
||||||
|
ActivationId: activationId,
|
||||||
|
FailedSyncRequest: 0,
|
||||||
|
},
|
||||||
|
Metrics: *snap,
|
||||||
|
SnapshotDate: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
err = lm.repository.InsertSnapshot(ctx, &payload)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lm *Manager) GetUsageFromClickHouse(ctx context.Context) (*model.UsageSnapshot, error) {
|
||||||
|
tableSizes := []TableSize{}
|
||||||
|
snap := model.UsageSnapshot{}
|
||||||
|
|
||||||
|
// get usage from clickhouse
|
||||||
|
query := `
|
||||||
|
SELECT
|
||||||
|
table,
|
||||||
|
disk_name,
|
||||||
|
sum(rows) as rows,
|
||||||
|
sum(data_uncompressed_bytes) AS uncompressed_bytes
|
||||||
|
FROM system.parts
|
||||||
|
WHERE active AND (database in ('signoz_logs', 'signoz_metrics', 'signoz_traces')) AND (table in ('logs','samples_v2', 'signoz_index_v2'))
|
||||||
|
GROUP BY
|
||||||
|
table,
|
||||||
|
disk_name
|
||||||
|
ORDER BY table
|
||||||
|
`
|
||||||
|
err := lm.clickhouseConn.Select(ctx, &tableSizes, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, val := range tableSizes {
|
||||||
|
switch val.Table {
|
||||||
|
case "logs":
|
||||||
|
if val.DiskName == "default" {
|
||||||
|
snap.CurrentLogSizeBytes = val.UncompressedBytes
|
||||||
|
} else {
|
||||||
|
snap.CurrentLogSizeBytesColdStorage = val.UncompressedBytes
|
||||||
|
}
|
||||||
|
case "samples_v2":
|
||||||
|
if val.DiskName == "default" {
|
||||||
|
snap.CurrentSamplesCount = val.Rows
|
||||||
|
} else {
|
||||||
|
snap.CurrentSamplesCountColdStorage = val.Rows
|
||||||
|
}
|
||||||
|
case "signoz_index_v2":
|
||||||
|
if val.DiskName == "default" {
|
||||||
|
snap.CurrentSpansCount = val.Rows
|
||||||
|
} else {
|
||||||
|
snap.CurrentSpansCountColdStorage = val.Rows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &snap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lm *Manager) UploadUsage(ctx context.Context) error {
|
||||||
|
snapshots, err := lm.repository.GetSnapshotsNotSynced(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(snapshots) <= 0 {
|
||||||
|
zap.S().Info("no snapshots to upload, skipping.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
zap.S().Info("uploading snapshots")
|
||||||
|
for _, snap := range snapshots {
|
||||||
|
metricsBytes, err := encryption.Decrypt([]byte(snap.ActivationId.String()[:32]), []byte(snap.Snapshot))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
metrics := model.UsageSnapshot{}
|
||||||
|
err = json.Unmarshal(metricsBytes, &metrics)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = lm.UploadUsageWithExponentalBackOff(ctx, model.UsagePayload{
|
||||||
|
UsageBase: model.UsageBase{
|
||||||
|
Id: snap.Id,
|
||||||
|
InstallationId: snap.InstallationId,
|
||||||
|
ActivationId: snap.ActivationId,
|
||||||
|
FailedSyncRequest: snap.FailedSyncRequest,
|
||||||
|
},
|
||||||
|
SnapshotDate: snap.CreatedAt,
|
||||||
|
Metrics: metrics,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lm *Manager) UploadUsageWithExponentalBackOff(ctx context.Context, payload model.UsagePayload) error {
|
||||||
|
for i := 1; i <= MaxRetries; i++ {
|
||||||
|
apiErr := licenseserver.SendUsage(ctx, &payload)
|
||||||
|
if apiErr != nil && i == MaxRetries {
|
||||||
|
err := lm.repository.IncrementFailedRequestCount(ctx, payload.Id)
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf("failed to updated the failure count for snapshot in DB : ", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
zap.S().Errorf("retries stopped : %v", zap.Error(err))
|
||||||
|
// not returning error here since it is captured in the failed count
|
||||||
|
return nil
|
||||||
|
} else if apiErr != nil {
|
||||||
|
// sleeping for exponential backoff
|
||||||
|
sleepDuration := RetryInterval * time.Duration(i)
|
||||||
|
zap.S().Errorf("failed to upload snapshot retrying after %v secs : %v", sleepDuration.Seconds(), zap.Error(apiErr.Err))
|
||||||
|
time.Sleep(sleepDuration)
|
||||||
|
|
||||||
|
// update the failed request count
|
||||||
|
err := lm.repository.IncrementFailedRequestCount(ctx, payload.Id)
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf("failed to updated the failure count for snapshot in DB : %v", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the database that it is synced
|
||||||
|
err := lm.repository.MoveToSynced(ctx, payload.Id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lm *Manager) Stop() {
|
||||||
|
close(lm.done)
|
||||||
|
atomic.StoreUint32(&locker, stateUnlocked)
|
||||||
|
<-lm.terminated
|
||||||
|
}
|
126
ee/query-service/usage/repository/repository.go
Normal file
126
ee/query-service/usage/repository/repository.go
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"go.signoz.io/signoz/ee/query-service/model"
|
||||||
|
"go.signoz.io/signoz/ee/query-service/usage/sqlite"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/utils/encryption"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MaxFailedSyncCount = 9 // a snapshot will be ignored if the max failed count is greater than or equal to 9
|
||||||
|
)
|
||||||
|
|
||||||
|
// Repository is usage Repository which stores usage snapshot in a secured DB
|
||||||
|
type Repository struct {
|
||||||
|
db *sqlx.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// New initiates a new usage Repository
|
||||||
|
func New(db *sqlx.DB) *Repository {
|
||||||
|
return &Repository{
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) Init(engine string) error {
|
||||||
|
switch engine {
|
||||||
|
case "sqlite3", "sqlite":
|
||||||
|
return sqlite.InitDB(r.db)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported db")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) InsertSnapshot(ctx context.Context, usage *model.UsagePayload) error {
|
||||||
|
|
||||||
|
snapshotBytes, err := json.Marshal(usage.Metrics)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
usage.Id = uuid.New()
|
||||||
|
|
||||||
|
encryptedSnapshot, err := encryption.Encrypt([]byte(usage.ActivationId.String()[:32]), snapshotBytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
query := `INSERT INTO usage(id, activation_id, snapshot)
|
||||||
|
VALUES ($1, $2, $3)`
|
||||||
|
_, err = r.db.ExecContext(ctx,
|
||||||
|
query,
|
||||||
|
usage.Id,
|
||||||
|
usage.ActivationId,
|
||||||
|
string(encryptedSnapshot),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf("error inserting usage data: %v", zap.Error(err))
|
||||||
|
return fmt.Errorf("failed to insert usage in db: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) MoveToSynced(ctx context.Context, id uuid.UUID) error {
|
||||||
|
|
||||||
|
query := `UPDATE usage
|
||||||
|
SET synced = 'true',
|
||||||
|
synced_at = $1
|
||||||
|
WHERE id = $2`
|
||||||
|
|
||||||
|
_, err := r.db.ExecContext(ctx, query, time.Now(), id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf("error in updating usage: %v", zap.Error(err))
|
||||||
|
return fmt.Errorf("failed to update usage in db: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) IncrementFailedRequestCount(ctx context.Context, id uuid.UUID) error {
|
||||||
|
|
||||||
|
query := `UPDATE usage SET failed_sync_request_count = failed_sync_request_count + 1 WHERE id = $1`
|
||||||
|
_, err := r.db.ExecContext(ctx, query, id)
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf("error in updating usage: %v", zap.Error(err))
|
||||||
|
return fmt.Errorf("failed to update usage in db: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) GetSnapshotsNotSynced(ctx context.Context) ([]*model.Usage, error) {
|
||||||
|
snapshots := []*model.Usage{}
|
||||||
|
|
||||||
|
query := `SELECT id,created_at, activation_id, snapshot, failed_sync_request_count from usage where synced!='true' and failed_sync_request_count < $1 order by created_at asc `
|
||||||
|
|
||||||
|
err := r.db.SelectContext(ctx, &snapshots, query, MaxFailedSyncCount)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return snapshots, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckSnapshotGtCreatedAt checks if there is any snapshot greater than the provided timestamp
|
||||||
|
func (r *Repository) CheckSnapshotGtCreatedAt(ctx context.Context, ts time.Time) (bool, error) {
|
||||||
|
|
||||||
|
var snapshots uint64
|
||||||
|
query := `SELECT count() from usage where created_at > '$1'`
|
||||||
|
|
||||||
|
err := r.db.QueryRowContext(ctx, query, ts).Scan(&snapshots)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return snapshots > 0, err
|
||||||
|
}
|
32
ee/query-service/usage/sqlite/init.go
Normal file
32
ee/query-service/usage/sqlite/init.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func InitDB(db *sqlx.DB) error {
|
||||||
|
var err error
|
||||||
|
if db == nil {
|
||||||
|
return fmt.Errorf("invalid db connection")
|
||||||
|
}
|
||||||
|
|
||||||
|
table_schema := `CREATE TABLE IF NOT EXISTS usage(
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
activation_id UUID,
|
||||||
|
snapshot TEXT,
|
||||||
|
synced BOOLEAN DEFAULT 'false',
|
||||||
|
synced_at TIMESTAMP,
|
||||||
|
failed_sync_request_count INTEGER DEFAULT 0
|
||||||
|
);
|
||||||
|
`
|
||||||
|
|
||||||
|
_, err = db.Exec(table_schema)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error in creating usage table: %v", err.Error())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
module go.signoz.io/query-service
|
module go.signoz.io/signoz
|
||||||
|
|
||||||
go 1.17
|
go 1.17
|
||||||
|
|
||||||
@ -16,11 +16,13 @@ require (
|
|||||||
github.com/minio/minio-go/v6 v6.0.57
|
github.com/minio/minio-go/v6 v6.0.57
|
||||||
github.com/oklog/oklog v0.3.2
|
github.com/oklog/oklog v0.3.2
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/prometheus/client_golang v0.9.0-pre1.0.20181001174001-0a8115f42e03
|
github.com/posthog/posthog-go v0.0.0-20220817142604-0b0bbf0f9c0f
|
||||||
github.com/prometheus/common v0.0.0-20180518154759-7600349dcfe1
|
github.com/prometheus/common v0.0.0-20180518154759-7600349dcfe1
|
||||||
github.com/prometheus/prometheus v2.5.0+incompatible
|
github.com/prometheus/prometheus v2.5.0+incompatible
|
||||||
github.com/prometheus/tsdb v0.0.0-20181003080831-0ce41118ed20
|
|
||||||
github.com/rs/cors v1.7.0
|
github.com/rs/cors v1.7.0
|
||||||
|
github.com/russellhaering/gosaml2 v0.8.0
|
||||||
|
github.com/russellhaering/goxmldsig v1.2.0
|
||||||
|
github.com/sethvargo/go-password v0.2.0
|
||||||
github.com/smartystreets/goconvey v1.6.4
|
github.com/smartystreets/goconvey v1.6.4
|
||||||
github.com/soheilhy/cmux v0.1.4
|
github.com/soheilhy/cmux v0.1.4
|
||||||
go.uber.org/zap v1.16.0
|
go.uber.org/zap v1.16.0
|
||||||
@ -29,12 +31,16 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/beevik/etree v1.1.0 // indirect
|
||||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible // indirect
|
github.com/form3tech-oss/jwt-go v3.2.2+incompatible // indirect
|
||||||
|
github.com/jonboulle/clockwork v0.2.2 // indirect
|
||||||
github.com/klauspost/cpuid v1.2.3 // indirect
|
github.com/klauspost/cpuid v1.2.3 // indirect
|
||||||
|
github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect
|
||||||
github.com/minio/md5-simd v1.1.0 // indirect
|
github.com/minio/md5-simd v1.1.0 // indirect
|
||||||
github.com/minio/sha256-simd v0.1.1 // indirect
|
github.com/minio/sha256-simd v0.1.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/posthog/posthog-go v0.0.0-20220817142604-0b0bbf0f9c0f // indirect
|
github.com/prometheus/client_golang v0.9.0-pre1.0.20181001174001-0a8115f42e03 // indirect
|
||||||
|
github.com/prometheus/tsdb v0.0.0-20181003080831-0ce41118ed20 // indirect
|
||||||
gopkg.in/ini.v1 v1.42.0 // indirect
|
gopkg.in/ini.v1 v1.42.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||||
)
|
)
|
||||||
@ -49,13 +55,13 @@ require (
|
|||||||
github.com/aws/aws-sdk-go v1.27.0 // indirect
|
github.com/aws/aws-sdk-go v1.27.0 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
|
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
|
||||||
github.com/cespare/xxhash v1.1.0 // indirect
|
github.com/cespare/xxhash v1.1.0
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.1 // indirect
|
github.com/felixge/httpsnoop v1.0.1 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||||
github.com/ghodss/yaml v1.0.0 // indirect
|
github.com/ghodss/yaml v1.0.0 // indirect
|
||||||
github.com/go-kit/kit v0.4.1-0.20170517165212-6964666de57c // indirect
|
github.com/go-kit/kit v0.4.1-0.20170517165212-6964666de57c
|
||||||
github.com/go-logfmt/logfmt v0.5.0 // indirect
|
github.com/go-logfmt/logfmt v0.5.0 // indirect
|
||||||
github.com/go-stack/stack v1.8.0 // indirect
|
github.com/go-stack/stack v1.8.0 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
@ -86,7 +92,6 @@ require (
|
|||||||
github.com/hashicorp/serf v0.8.1-0.20161007004122-1d4fa605f6ff // indirect
|
github.com/hashicorp/serf v0.8.1-0.20161007004122-1d4fa605f6ff // indirect
|
||||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||||
github.com/jtolds/gls v4.20.0+incompatible // indirect
|
github.com/jtolds/gls v4.20.0+incompatible // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
|
||||||
github.com/lib/pq v1.10.0 // indirect
|
github.com/lib/pq v1.10.0 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||||
github.com/miekg/dns v1.0.4 // indirect
|
github.com/miekg/dns v1.0.4 // indirect
|
||||||
@ -97,7 +102,7 @@ require (
|
|||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 // indirect
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 // indirect
|
||||||
github.com/oklog/run v1.1.0 // indirect
|
github.com/oklog/run v1.1.0 // indirect
|
||||||
github.com/oklog/ulid v0.3.1-0.20170117200651-66bb6560562f // indirect
|
github.com/oklog/ulid v0.3.1-0.20170117200651-66bb6560562f // indirect
|
||||||
github.com/opentracing/opentracing-go v1.1.0 // indirect
|
github.com/opentracing/opentracing-go v1.1.0
|
||||||
github.com/pascaldekloe/goe v0.1.0 // indirect
|
github.com/pascaldekloe/goe v0.1.0 // indirect
|
||||||
github.com/paulmach/orb v0.4.0 // indirect
|
github.com/paulmach/orb v0.4.0 // indirect
|
||||||
github.com/peterbourgon/diskv v2.0.2-0.20180312054125-0646ccaebea1+incompatible // indirect
|
github.com/peterbourgon/diskv v2.0.2-0.20180312054125-0646ccaebea1+incompatible // indirect
|
||||||
@ -120,7 +125,7 @@ require (
|
|||||||
go.uber.org/atomic v1.6.0 // indirect
|
go.uber.org/atomic v1.6.0 // indirect
|
||||||
go.uber.org/multierr v1.5.0 // indirect
|
go.uber.org/multierr v1.5.0 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
|
||||||
golang.org/x/net v0.0.0-20211013171255-e13a2654a71e // indirect
|
golang.org/x/net v0.0.0-20211013171255-e13a2654a71e
|
||||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 // indirect
|
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 // indirect
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||||
golang.org/x/sys v0.0.0-20211110154304-99a53858aa08 // indirect
|
golang.org/x/sys v0.0.0-20211110154304-99a53858aa08 // indirect
|
||||||
@ -133,10 +138,9 @@ require (
|
|||||||
google.golang.org/grpc v1.41.0
|
google.golang.org/grpc v1.41.0
|
||||||
google.golang.org/grpc/examples v0.0.0-20210803221256-6ba56c814be7 // indirect
|
google.golang.org/grpc/examples v0.0.0-20210803221256-6ba56c814be7 // indirect
|
||||||
google.golang.org/protobuf v1.27.1 // indirect
|
google.golang.org/protobuf v1.27.1 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
|
||||||
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect
|
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
k8s.io/api v0.0.0-20180628040859-072894a440bd // indirect
|
k8s.io/api v0.0.0-20180628040859-072894a440bd // indirect
|
||||||
k8s.io/client-go v8.0.0+incompatible // indirect
|
k8s.io/client-go v8.0.0+incompatible // indirect
|
||||||
)
|
)
|
@ -70,6 +70,8 @@ github.com/auth0/go-jwt-middleware v1.0.1/go.mod h1:YSeUX3z6+TF2H+7padiEqNJ73Zy9
|
|||||||
github.com/aws/aws-sdk-go v1.13.44-0.20180507225419-00862f899353/go.mod h1:ZRmQr0FajVIyZ4ZzBYKG5P3ZqPz9IHG41ZoMu1ADI3k=
|
github.com/aws/aws-sdk-go v1.13.44-0.20180507225419-00862f899353/go.mod h1:ZRmQr0FajVIyZ4ZzBYKG5P3ZqPz9IHG41ZoMu1ADI3k=
|
||||||
github.com/aws/aws-sdk-go v1.27.0 h1:0xphMHGMLBrPMfxR2AmVjZKcMEESEgWF8Kru94BNByk=
|
github.com/aws/aws-sdk-go v1.27.0 h1:0xphMHGMLBrPMfxR2AmVjZKcMEESEgWF8Kru94BNByk=
|
||||||
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||||
|
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
|
||||||
|
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
||||||
github.com/beorn7/perks v0.0.0-20160229213445-3ac7bf7a47d1/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v0.0.0-20160229213445-3ac7bf7a47d1/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
@ -292,10 +294,10 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfC
|
|||||||
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||||
github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w=
|
github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w=
|
||||||
github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=
|
github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=
|
||||||
|
github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
|
||||||
|
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||||
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
|
|
||||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
@ -310,8 +312,9 @@ github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgo
|
|||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
@ -320,6 +323,8 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
|||||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E=
|
github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E=
|
||||||
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
|
github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU=
|
||||||
|
github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=
|
||||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU=
|
github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU=
|
||||||
@ -344,7 +349,6 @@ github.com/mkevac/debugcharts v0.0.0-20191222103121-ae1c48aa8615/go.mod h1:Ad7oe
|
|||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
@ -373,6 +377,7 @@ github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM
|
|||||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||||
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
|
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
|
||||||
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
github.com/pkg/errors v0.8.1-0.20161029093637-248dadf4e906/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1-0.20161029093637-248dadf4e906/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
@ -394,8 +399,15 @@ github.com/prometheus/tsdb v0.0.0-20181003080831-0ce41118ed20 h1:Jh/eKJuru9z9u3r
|
|||||||
github.com/prometheus/tsdb v0.0.0-20181003080831-0ce41118ed20/go.mod h1:lFf/o1J2a31WmWQbxYXfY1azJK5Xp5D8hwKMnVMBTGU=
|
github.com/prometheus/tsdb v0.0.0-20181003080831-0ce41118ed20/go.mod h1:lFf/o1J2a31WmWQbxYXfY1azJK5Xp5D8hwKMnVMBTGU=
|
||||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
|
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||||
|
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||||
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
||||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||||
|
github.com/russellhaering/gosaml2 v0.8.0 h1:rm1Gc09/UoEsKGTSFvg8VCHJLY3wrP4BWjC+1ov0qCo=
|
||||||
|
github.com/russellhaering/gosaml2 v0.8.0/go.mod h1:byViER/1YPUa0Puj9ROZblpoq2jsE7h/CJmitzX0geU=
|
||||||
|
github.com/russellhaering/goxmldsig v1.2.0 h1:Y6GTTc9Un5hCxSzVz4UIWQ/zuVwDvzJk80guqzwx6Vg=
|
||||||
|
github.com/russellhaering/goxmldsig v1.2.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/samuel/go-zookeeper v0.0.0-20161028232340-1d7be4effb13/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
github.com/samuel/go-zookeeper v0.0.0-20161028232340-1d7be4effb13/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
||||||
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da h1:p3Vo3i64TCLY7gIfzeQaUJ+kppEO5WQG3cL8iE8tGHU=
|
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da h1:p3Vo3i64TCLY7gIfzeQaUJ+kppEO5WQG3cL8iE8tGHU=
|
||||||
@ -405,6 +417,8 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUt
|
|||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||||
github.com/segmentio/backo-go v1.0.0 h1:kbOAtGJY2DqOR0jfRkYEorx/b18RgtepGtY3+Cpe6qA=
|
github.com/segmentio/backo-go v1.0.0 h1:kbOAtGJY2DqOR0jfRkYEorx/b18RgtepGtY3+Cpe6qA=
|
||||||
github.com/segmentio/backo-go v1.0.0/go.mod h1:kJ9mm9YmoWSkk+oQ+5Cj8DEoRCX2JT6As4kEtIIOp1M=
|
github.com/segmentio/backo-go v1.0.0/go.mod h1:kJ9mm9YmoWSkk+oQ+5Cj8DEoRCX2JT6As4kEtIIOp1M=
|
||||||
|
github.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc7i/UHI=
|
||||||
|
github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE=
|
||||||
github.com/shirou/gopsutil v2.19.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
github.com/shirou/gopsutil v2.19.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
|
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
|
||||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
@ -11,7 +11,7 @@ RUN export GOOS=$(echo ${TARGETPLATFORM} | cut -d / -f1) && \
|
|||||||
export GOARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2)
|
export GOARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2)
|
||||||
|
|
||||||
# Prepare and enter src directory
|
# Prepare and enter src directory
|
||||||
WORKDIR /go/src/github.com/signoz/signoz/pkg/query-service
|
WORKDIR /go/src/github.com/signoz/signoz
|
||||||
|
|
||||||
# Cache dependencies
|
# Cache dependencies
|
||||||
ADD go.mod .
|
ADD go.mod .
|
||||||
@ -20,8 +20,10 @@ RUN go mod download -x
|
|||||||
|
|
||||||
# Add the sources and proceed with build
|
# Add the sources and proceed with build
|
||||||
ADD . .
|
ADD . .
|
||||||
RUN go build -tags timetzdata -a -ldflags "-linkmode external -extldflags '-static' -s -w $LD_FLAGS" -o ./bin/query-service ./main.go
|
RUN cd pkg/query-service \
|
||||||
RUN chmod +x ./bin/query-service
|
&& go build -tags timetzdata -a -o ./bin/query-service \
|
||||||
|
-ldflags "-linkmode external -extldflags '-static' -s -w $LD_FLAGS" \
|
||||||
|
&& chmod +x ./bin/query-service
|
||||||
|
|
||||||
|
|
||||||
# use a minimal alpine image
|
# use a minimal alpine image
|
||||||
@ -39,7 +41,8 @@ WORKDIR /root
|
|||||||
# copy the binary from builder
|
# copy the binary from builder
|
||||||
COPY --from=builder /go/src/github.com/signoz/signoz/pkg/query-service/bin/query-service .
|
COPY --from=builder /go/src/github.com/signoz/signoz/pkg/query-service/bin/query-service .
|
||||||
|
|
||||||
COPY config/prometheus.yml /root/config/prometheus.yml
|
# copy prometheus YAML config
|
||||||
|
COPY pkg/query-service/config/prometheus.yml /root/config/prometheus.yml
|
||||||
|
|
||||||
# run the binary
|
# run the binary
|
||||||
ENTRYPOINT ["./query-service"]
|
ENTRYPOINT ["./query-service"]
|
||||||
|
@ -39,11 +39,11 @@ import (
|
|||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
|
||||||
promModel "github.com/prometheus/common/model"
|
promModel "github.com/prometheus/common/model"
|
||||||
"go.signoz.io/query-service/app/logs"
|
"go.signoz.io/signoz/pkg/query-service/app/logs"
|
||||||
"go.signoz.io/query-service/constants"
|
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
am "go.signoz.io/query-service/integrations/alertManager"
|
am "go.signoz.io/signoz/pkg/query-service/integrations/alertManager"
|
||||||
"go.signoz.io/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
"go.signoz.io/query-service/utils"
|
"go.signoz.io/signoz/pkg/query-service/utils"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/gosimple/slug"
|
"github.com/gosimple/slug"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"go.signoz.io/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"go.signoz.io/query-service/constants"
|
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -7,8 +7,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"go.signoz.io/query-service/constants"
|
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
"go.signoz.io/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
var operatorMapping = map[string]string{
|
var operatorMapping = map[string]string{
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
"go.signoz.io/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
var correctQueriesTest = []struct {
|
var correctQueriesTest = []struct {
|
||||||
|
@ -4,8 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"go.signoz.io/query-service/constants"
|
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
"go.signoz.io/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ValidateUpdateFieldPayload(field *model.UpdateField) error {
|
func ValidateUpdateFieldPayload(field *model.UpdateField) error {
|
||||||
|
@ -6,8 +6,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/SigNoz/govaluate"
|
"github.com/SigNoz/govaluate"
|
||||||
"go.signoz.io/query-service/constants"
|
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
"go.signoz.io/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
"go.signoz.io/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBuildQuery(t *testing.T) {
|
func TestBuildQuery(t *testing.T) {
|
||||||
|
@ -12,9 +12,9 @@ import (
|
|||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
promModel "github.com/prometheus/common/model"
|
promModel "github.com/prometheus/common/model"
|
||||||
|
|
||||||
"go.signoz.io/query-service/auth"
|
"go.signoz.io/signoz/pkg/query-service/auth"
|
||||||
"go.signoz.io/query-service/constants"
|
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
"go.signoz.io/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
var allowedFunctions = []string{"count", "ratePerSec", "sum", "avg", "min", "max", "p50", "p90", "p95", "p99"}
|
var allowedFunctions = []string{"count", "ratePerSec", "sum", "avg", "min", "max", "p50", "p90", "p95", "p99"}
|
||||||
|
@ -6,8 +6,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"go.signoz.io/query-service/app/metrics"
|
"go.signoz.io/signoz/pkg/query-service/app/metrics"
|
||||||
"go.signoz.io/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func validateQueryRangeParamsV2(qp *model.QueryRangeParamsV2) error {
|
func validateQueryRangeParamsV2(qp *model.QueryRangeParamsV2) error {
|
||||||
|
@ -8,8 +8,8 @@ import (
|
|||||||
"github.com/smartystreets/assertions/should"
|
"github.com/smartystreets/assertions/should"
|
||||||
|
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
"go.signoz.io/query-service/app/metrics"
|
"go.signoz.io/signoz/pkg/query-service/app/metrics"
|
||||||
"go.signoz.io/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseFilterSingleFilter(t *testing.T) {
|
func TestParseFilterSingleFilter(t *testing.T) {
|
||||||
|
@ -15,17 +15,17 @@ import (
|
|||||||
|
|
||||||
"github.com/rs/cors"
|
"github.com/rs/cors"
|
||||||
"github.com/soheilhy/cmux"
|
"github.com/soheilhy/cmux"
|
||||||
"go.signoz.io/query-service/app/clickhouseReader"
|
"go.signoz.io/signoz/pkg/query-service/app/clickhouseReader"
|
||||||
"go.signoz.io/query-service/app/dashboards"
|
"go.signoz.io/signoz/pkg/query-service/app/dashboards"
|
||||||
"go.signoz.io/query-service/constants"
|
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
"go.signoz.io/query-service/dao"
|
"go.signoz.io/signoz/pkg/query-service/dao"
|
||||||
"go.signoz.io/query-service/healthcheck"
|
"go.signoz.io/signoz/pkg/query-service/healthcheck"
|
||||||
am "go.signoz.io/query-service/integrations/alertManager"
|
am "go.signoz.io/signoz/pkg/query-service/integrations/alertManager"
|
||||||
"go.signoz.io/query-service/interfaces"
|
"go.signoz.io/signoz/pkg/query-service/interfaces"
|
||||||
pqle "go.signoz.io/query-service/pqlEngine"
|
pqle "go.signoz.io/signoz/pkg/query-service/pqlEngine"
|
||||||
"go.signoz.io/query-service/rules"
|
"go.signoz.io/signoz/pkg/query-service/rules"
|
||||||
"go.signoz.io/query-service/telemetry"
|
"go.signoz.io/signoz/pkg/query-service/telemetry"
|
||||||
"go.signoz.io/query-service/utils"
|
"go.signoz.io/signoz/pkg/query-service/utils"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -97,7 +97,11 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
telemetry.GetInstance().SetReader(reader)
|
telemetry.GetInstance().SetReader(reader)
|
||||||
apiHandler, err := NewAPIHandler(&reader, dao.DB(), rm)
|
apiHandler, err := NewAPIHandler(APIHandlerOpts{
|
||||||
|
Reader: reader,
|
||||||
|
AppDao: dao.DB(),
|
||||||
|
RuleManager: rm,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,11 @@ import (
|
|||||||
"github.com/golang-jwt/jwt"
|
"github.com/golang-jwt/jwt"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"go.signoz.io/query-service/constants"
|
|
||||||
"go.signoz.io/query-service/dao"
|
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
"go.signoz.io/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/dao"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/utils"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
@ -119,7 +121,7 @@ func GetInvite(ctx context.Context, token string) (*model.InvitationResponseObje
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateInvite(ctx context.Context, req *RegisterRequest) (*model.InvitationObject, error) {
|
func ValidateInvite(ctx context.Context, req *RegisterRequest) (*model.InvitationObject, error) {
|
||||||
invitation, err := dao.DB().GetInviteFromEmail(ctx, req.Email)
|
invitation, err := dao.DB().GetInviteFromEmail(ctx, req.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err.Err, "Failed to read from DB")
|
return nil, errors.Wrap(err.Err, "Failed to read from DB")
|
||||||
@ -207,65 +209,43 @@ type RegisterRequest struct {
|
|||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
InviteToken string `json:"token"`
|
InviteToken string `json:"token"`
|
||||||
|
|
||||||
|
// reference URL to track where the register request is coming from
|
||||||
|
SourceUrl string `json:"sourceUrl"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register registers a new user. For the first register request, it doesn't need an invite token
|
func RegisterFirstUser(ctx context.Context, req *RegisterRequest) (*model.User, *model.ApiError) {
|
||||||
// and also the first registration is an enforced ADMIN registration. Every subsequent request will
|
|
||||||
// need an invite token to go through.
|
|
||||||
func Register(ctx context.Context, req *RegisterRequest) *model.ApiError {
|
|
||||||
|
|
||||||
zap.S().Debugf("Got a register request for email: %v\n", req.Email)
|
if req.Email == "" {
|
||||||
|
return nil, model.BadRequest(model.ErrEmailRequired{})
|
||||||
// TODO(Ahsan): We should optimize it, shouldn't make an extra DB call everytime to know if
|
|
||||||
// this is the first register request.
|
|
||||||
users, apiErr := dao.DB().GetUsers(ctx)
|
|
||||||
if apiErr != nil {
|
|
||||||
zap.S().Debugf("GetUser failed, err: %v\n", apiErr.Err)
|
|
||||||
return apiErr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var groupName, orgId string
|
if req.Password == "" {
|
||||||
|
return nil, model.BadRequest(model.ErrPasswordRequired{})
|
||||||
// If there are no user, then this first user is granted Admin role. Also, an org is created
|
|
||||||
// based on the request. Any other user can't use any other org name, if they do then
|
|
||||||
// registration will fail because of foreign key violation while create user.
|
|
||||||
// TODO(Ahsan): We need to re-work this logic for the case of multi-tenant system.
|
|
||||||
if len(users) == 0 {
|
|
||||||
org, apiErr := dao.DB().CreateOrg(ctx, &model.Organization{Name: req.OrgName})
|
|
||||||
if apiErr != nil {
|
|
||||||
zap.S().Debugf("CreateOrg failed, err: %v\n", apiErr.Err)
|
|
||||||
return apiErr
|
|
||||||
}
|
|
||||||
groupName = constants.AdminGroup
|
|
||||||
orgId = org.Id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(users) > 0 {
|
groupName := constants.AdminGroup
|
||||||
inv, err := validateInvite(ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
return &model.ApiError{Err: err, Typ: model.ErrorUnauthorized}
|
|
||||||
}
|
|
||||||
org, apiErr := dao.DB().GetOrgByName(ctx, req.OrgName)
|
|
||||||
if apiErr != nil {
|
|
||||||
zap.S().Debugf("GetOrgByName failed, err: %v\n", apiErr.Err)
|
|
||||||
return apiErr
|
|
||||||
}
|
|
||||||
|
|
||||||
groupName = inv.Role
|
org, apierr := dao.DB().CreateOrg(ctx,
|
||||||
if org != nil {
|
&model.Organization{Name: req.OrgName})
|
||||||
orgId = org.Id
|
if apierr != nil {
|
||||||
}
|
zap.S().Debugf("CreateOrg failed, err: %v\n", zap.Error(apierr.ToError()))
|
||||||
|
return nil, apierr
|
||||||
}
|
}
|
||||||
|
|
||||||
group, apiErr := dao.DB().GetGroupByName(ctx, groupName)
|
group, apiErr := dao.DB().GetGroupByName(ctx, groupName)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
zap.S().Debugf("GetGroupByName failed, err: %v\n", apiErr.Err)
|
zap.S().Debugf("GetGroupByName failed, err: %v\n", apiErr.Err)
|
||||||
return apiErr
|
return nil, apiErr
|
||||||
}
|
}
|
||||||
|
|
||||||
hash, err := passwordHash(req.Password)
|
var hash string
|
||||||
|
var err error
|
||||||
|
|
||||||
|
hash, err = passwordHash(req.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &model.ApiError{Err: err, Typ: model.ErrorUnauthorized}
|
zap.S().Errorf("failed to generate password hash when registering a user", zap.Error(err))
|
||||||
|
return nil, model.InternalError(model.ErrSignupFailed{})
|
||||||
}
|
}
|
||||||
|
|
||||||
user := &model.User{
|
user := &model.User{
|
||||||
@ -276,17 +256,118 @@ func Register(ctx context.Context, req *RegisterRequest) *model.ApiError {
|
|||||||
CreatedAt: time.Now().Unix(),
|
CreatedAt: time.Now().Unix(),
|
||||||
ProfilePirctureURL: "", // Currently unused
|
ProfilePirctureURL: "", // Currently unused
|
||||||
GroupId: group.Id,
|
GroupId: group.Id,
|
||||||
OrgId: orgId,
|
OrgId: org.Id,
|
||||||
|
}
|
||||||
|
|
||||||
|
return dao.DB().CreateUser(ctx, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterInvitedUser handles registering a invited user
|
||||||
|
func RegisterInvitedUser(ctx context.Context, req *RegisterRequest, nopassword bool) (*model.User, *model.ApiError) {
|
||||||
|
|
||||||
|
if req.InviteToken == "" {
|
||||||
|
return nil, model.BadRequest(fmt.Errorf("invite token is required"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !nopassword && req.Password == "" {
|
||||||
|
return nil, model.BadRequest(model.ErrPasswordRequired{})
|
||||||
|
}
|
||||||
|
|
||||||
|
invite, err := ValidateInvite(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf("failed to validate invite token", err)
|
||||||
|
return nil, model.BadRequest(model.ErrSignupFailed{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// checking if user email already exists, this is defensive but
|
||||||
|
// required as delete invitation and user creation dont happen
|
||||||
|
// in the same transaction at the end of this function
|
||||||
|
userPayload, apierr := dao.DB().GetUserByEmail(ctx, invite.Email)
|
||||||
|
if apierr != nil {
|
||||||
|
zap.S().Debugf("failed to get user by email", apierr.Err)
|
||||||
|
return nil, apierr
|
||||||
|
}
|
||||||
|
|
||||||
|
if userPayload != nil {
|
||||||
|
// user already exists
|
||||||
|
return &userPayload.User, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if invite.OrgId == "" {
|
||||||
|
zap.S().Errorf("failed to find org in the invite")
|
||||||
|
return nil, model.InternalError(fmt.Errorf("invalid invite, org not found"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if invite.Role == "" {
|
||||||
|
// if role is not provided, default to viewer
|
||||||
|
invite.Role = constants.ViewerGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
group, apiErr := dao.DB().GetGroupByName(ctx, invite.Role)
|
||||||
|
if apiErr != nil {
|
||||||
|
zap.S().Debugf("GetGroupByName failed, err: %v\n", apiErr.Err)
|
||||||
|
return nil, model.InternalError(model.ErrSignupFailed{})
|
||||||
|
}
|
||||||
|
|
||||||
|
var hash string
|
||||||
|
|
||||||
|
// check if password is not empty, as for SSO case it can be
|
||||||
|
if req.Password != "" {
|
||||||
|
hash, err = passwordHash(req.Password)
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf("failed to generate password hash when registering a user", zap.Error(err))
|
||||||
|
return nil, model.InternalError(model.ErrSignupFailed{})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hash, err = passwordHash(utils.GeneratePassowrd())
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf("failed to generate password hash when registering a user", zap.Error(err))
|
||||||
|
return nil, model.InternalError(model.ErrSignupFailed{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user := &model.User{
|
||||||
|
Id: uuid.NewString(),
|
||||||
|
Name: req.Name,
|
||||||
|
Email: req.Email,
|
||||||
|
Password: hash,
|
||||||
|
CreatedAt: time.Now().Unix(),
|
||||||
|
ProfilePirctureURL: "", // Currently unused
|
||||||
|
GroupId: group.Id,
|
||||||
|
OrgId: invite.OrgId,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(Ahsan): Ideally create user and delete invitation should happen in a txn.
|
// TODO(Ahsan): Ideally create user and delete invitation should happen in a txn.
|
||||||
_, apiErr = dao.DB().CreateUser(ctx, user)
|
user, apiErr = dao.DB().CreateUser(ctx, user)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
zap.S().Debugf("CreateUser failed, err: %v\n", apiErr.Err)
|
zap.S().Debugf("CreateUser failed, err: %v\n", apiErr.Err)
|
||||||
return apiErr
|
return nil, apiErr
|
||||||
}
|
}
|
||||||
|
|
||||||
return dao.DB().DeleteInvitation(ctx, user.Email)
|
apiErr = dao.DB().DeleteInvitation(ctx, user.Email)
|
||||||
|
if apiErr != nil {
|
||||||
|
zap.S().Debugf("delete invitation failed, err: %v\n", apiErr.Err)
|
||||||
|
return nil, apiErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register registers a new user. For the first register request, it doesn't need an invite token
|
||||||
|
// and also the first registration is an enforced ADMIN registration. Every subsequent request will
|
||||||
|
// need an invite token to go through.
|
||||||
|
func Register(ctx context.Context, req *RegisterRequest) (*model.User, *model.ApiError) {
|
||||||
|
users, err := dao.DB().GetUsers(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.InternalError(fmt.Errorf("failed to get user count"))
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(users) {
|
||||||
|
case 0:
|
||||||
|
return RegisterFirstUser(ctx, req)
|
||||||
|
default:
|
||||||
|
return RegisterInvitedUser(ctx, req, false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login method returns access and refresh tokens on successful login, else it errors out.
|
// Login method returns access and refresh tokens on successful login, else it errors out.
|
||||||
@ -299,39 +380,15 @@ func Login(ctx context.Context, request *model.LoginRequest) (*model.LoginRespon
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
accessJwtExpiry := time.Now().Add(JwtExpiry).Unix()
|
userjwt, err := GenerateJWTForUser(&user.User)
|
||||||
|
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
|
||||||
"id": user.Id,
|
|
||||||
"gid": user.GroupId,
|
|
||||||
"email": user.Email,
|
|
||||||
"exp": accessJwtExpiry,
|
|
||||||
})
|
|
||||||
|
|
||||||
accessJwt, err := token.SignedString([]byte(JwtSecret))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Errorf("failed to encode jwt: %v", err)
|
zap.S().Debugf("Failed to generate JWT against login creds, %v", err)
|
||||||
}
|
return nil, err
|
||||||
|
|
||||||
refreshJwtExpiry := time.Now().Add(JwtRefresh).Unix()
|
|
||||||
token = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
|
||||||
"id": user.Id,
|
|
||||||
"gid": user.GroupId,
|
|
||||||
"email": user.Email,
|
|
||||||
"exp": refreshJwtExpiry,
|
|
||||||
})
|
|
||||||
|
|
||||||
refreshJwt, err := token.SignedString([]byte(JwtSecret))
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Errorf("failed to encode jwt: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &model.LoginResponse{
|
return &model.LoginResponse{
|
||||||
AccessJwt: accessJwt,
|
UserJwtObject: userjwt,
|
||||||
AccessJwtExpiry: accessJwtExpiry,
|
UserId: user.User.Id,
|
||||||
RefreshJwt: refreshJwt,
|
|
||||||
RefreshJwtExpiry: refreshJwtExpiry,
|
|
||||||
UserId: user.Id,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -375,3 +432,35 @@ func passwordMatch(hash, password string) bool {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GenerateJWTForUser(user *model.User) (model.UserJwtObject, error) {
|
||||||
|
j := model.UserJwtObject{}
|
||||||
|
var err error
|
||||||
|
j.AccessJwtExpiry = time.Now().Add(JwtExpiry).Unix()
|
||||||
|
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||||
|
"id": user.Id,
|
||||||
|
"gid": user.GroupId,
|
||||||
|
"email": user.Email,
|
||||||
|
"exp": j.AccessJwtExpiry,
|
||||||
|
})
|
||||||
|
|
||||||
|
j.AccessJwt, err = token.SignedString([]byte(JwtSecret))
|
||||||
|
if err != nil {
|
||||||
|
return j, errors.Errorf("failed to encode jwt: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
j.RefreshJwtExpiry = time.Now().Add(JwtRefresh).Unix()
|
||||||
|
token = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||||
|
"id": user.Id,
|
||||||
|
"gid": user.GroupId,
|
||||||
|
"email": user.Email,
|
||||||
|
"exp": j.RefreshJwtExpiry,
|
||||||
|
})
|
||||||
|
|
||||||
|
j.RefreshJwt, err = token.SignedString([]byte(JwtSecret))
|
||||||
|
if err != nil {
|
||||||
|
return j, errors.Errorf("failed to encode jwt: %v", err)
|
||||||
|
}
|
||||||
|
return j, nil
|
||||||
|
}
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
jwtmiddleware "github.com/auth0/go-jwt-middleware"
|
jwtmiddleware "github.com/auth0/go-jwt-middleware"
|
||||||
"github.com/golang-jwt/jwt"
|
"github.com/golang-jwt/jwt"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"go.signoz.io/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"google.golang.org/grpc/metadata"
|
"google.golang.org/grpc/metadata"
|
||||||
)
|
)
|
||||||
|
@ -7,9 +7,9 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"go.signoz.io/query-service/constants"
|
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
"go.signoz.io/query-service/dao"
|
"go.signoz.io/signoz/pkg/query-service/dao"
|
||||||
"go.signoz.io/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Group struct {
|
type Group struct {
|
||||||
|
@ -5,8 +5,8 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"go.signoz.io/query-service/constants"
|
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
"go.signoz.io/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"go.signoz.io/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"go.signoz.io/query-service/dao/sqlite"
|
"go.signoz.io/signoz/pkg/query-service/dao/sqlite"
|
||||||
)
|
)
|
||||||
|
|
||||||
var db ModelDao
|
var db ModelDao
|
||||||
@ -24,6 +24,11 @@ func InitDao(engine, path string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetDB is used by ee for setting modelDAO
|
||||||
|
func SetDB(m ModelDao) {
|
||||||
|
db = m
|
||||||
|
}
|
||||||
|
|
||||||
func DB() ModelDao {
|
func DB() ModelDao {
|
||||||
if db == nil {
|
if db == nil {
|
||||||
// Should never reach here
|
// Should never reach here
|
||||||
|
@ -3,7 +3,7 @@ package dao
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"go.signoz.io/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ModelDao interface {
|
type ModelDao interface {
|
||||||
|
@ -6,9 +6,9 @@ import (
|
|||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"go.signoz.io/query-service/constants"
|
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
"go.signoz.io/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
"go.signoz.io/query-service/telemetry"
|
"go.signoz.io/signoz/pkg/query-service/telemetry"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -88,6 +88,11 @@ func InitDB(dataSourceName string) (*ModelDaoSqlite, error) {
|
|||||||
return mds, nil
|
return mds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DB returns database connection
|
||||||
|
func (mds *ModelDaoSqlite) DB() *sqlx.DB {
|
||||||
|
return mds.db
|
||||||
|
}
|
||||||
|
|
||||||
// initializeOrgPreferences initializes in-memory telemetry settings. It is planned to have
|
// initializeOrgPreferences initializes in-memory telemetry settings. It is planned to have
|
||||||
// multiple orgs in the system. In case of multiple orgs, there will be separate instance
|
// multiple orgs in the system. In case of multiple orgs, there will be separate instance
|
||||||
// of in-memory telemetry for each of the org, having their own settings. As of now, we only
|
// of in-memory telemetry for each of the org, having their own settings. As of now, we only
|
||||||
|
@ -7,8 +7,8 @@ import (
|
|||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"go.signoz.io/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
"go.signoz.io/query-service/telemetry"
|
"go.signoz.io/signoz/pkg/query-service/telemetry"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (mds *ModelDaoSqlite) CreateInviteEntry(ctx context.Context,
|
func (mds *ModelDaoSqlite) CreateInviteEntry(ctx context.Context,
|
||||||
@ -291,6 +291,13 @@ func (mds *ModelDaoSqlite) GetUser(ctx context.Context,
|
|||||||
func (mds *ModelDaoSqlite) GetUserByEmail(ctx context.Context,
|
func (mds *ModelDaoSqlite) GetUserByEmail(ctx context.Context,
|
||||||
email string) (*model.UserPayload, *model.ApiError) {
|
email string) (*model.UserPayload, *model.ApiError) {
|
||||||
|
|
||||||
|
if email == "" {
|
||||||
|
return nil, &model.ApiError{
|
||||||
|
Typ: model.ErrorBadData,
|
||||||
|
Err: fmt.Errorf("empty email address"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
users := []model.UserPayload{}
|
users := []model.UserPayload{}
|
||||||
query := `select
|
query := `select
|
||||||
u.id,
|
u.id,
|
||||||
|
@ -5,11 +5,12 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go.signoz.io/query-service/constants"
|
|
||||||
"go.signoz.io/query-service/model"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
neturl "net/url"
|
neturl "net/url"
|
||||||
|
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
const contentType = "application/json"
|
const contentType = "application/json"
|
||||||
|
@ -2,8 +2,9 @@ package alertManager
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"go.signoz.io/query-service/utils/labels"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/utils/labels"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Receiver configuration provides configuration on how to contact a receiver.
|
// Receiver configuration provides configuration on how to contact a receiver.
|
||||||
|
10
pkg/query-service/interfaces/featureLookup.go
Normal file
10
pkg/query-service/interfaces/featureLookup.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package interfaces
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FeatureLookup interface {
|
||||||
|
CheckFeature(f string) error
|
||||||
|
GetFeatureFlags() model.FeatureSet
|
||||||
|
}
|
@ -7,8 +7,8 @@ import (
|
|||||||
"github.com/prometheus/prometheus/promql"
|
"github.com/prometheus/prometheus/promql"
|
||||||
"github.com/prometheus/prometheus/storage"
|
"github.com/prometheus/prometheus/storage"
|
||||||
"github.com/prometheus/prometheus/util/stats"
|
"github.com/prometheus/prometheus/util/stats"
|
||||||
am "go.signoz.io/query-service/integrations/alertManager"
|
am "go.signoz.io/signoz/pkg/query-service/integrations/alertManager"
|
||||||
"go.signoz.io/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Reader interface {
|
type Reader interface {
|
||||||
|
@ -7,10 +7,10 @@ import (
|
|||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"go.signoz.io/query-service/app"
|
"go.signoz.io/signoz/pkg/query-service/app"
|
||||||
"go.signoz.io/query-service/auth"
|
"go.signoz.io/signoz/pkg/query-service/auth"
|
||||||
"go.signoz.io/query-service/constants"
|
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
"go.signoz.io/query-service/version"
|
"go.signoz.io/signoz/pkg/query-service/version"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
|
@ -32,11 +32,15 @@ type LoginRequest struct {
|
|||||||
RefreshToken string `json:"refreshToken"`
|
RefreshToken string `json:"refreshToken"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LoginResponse struct {
|
type UserJwtObject struct {
|
||||||
AccessJwt string `json:"accessJwt"`
|
AccessJwt string `json:"accessJwt"`
|
||||||
AccessJwtExpiry int64 `json:"accessJwtExpiry"`
|
AccessJwtExpiry int64 `json:"accessJwtExpiry"`
|
||||||
RefreshJwt string `json:"refreshJwt"`
|
RefreshJwt string `json:"refreshJwt"`
|
||||||
RefreshJwtExpiry int64 `json:"refreshJwtExpiry"`
|
RefreshJwtExpiry int64 `json:"refreshJwtExpiry"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoginResponse struct {
|
||||||
|
UserJwtObject
|
||||||
UserId string `json:"userId"`
|
UserId string `json:"userId"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ type Organization struct {
|
|||||||
HasOptedUpdates bool `json:"hasOptedUpdates" db:"has_opted_updates"`
|
HasOptedUpdates bool `json:"hasOptedUpdates" db:"has_opted_updates"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InvitationObject represents the token object stored in the db
|
||||||
type InvitationObject struct {
|
type InvitationObject struct {
|
||||||
Id string `json:"id" db:"id"`
|
Id string `json:"id" db:"id"`
|
||||||
Email string `json:"email" db:"email"`
|
Email string `json:"email" db:"email"`
|
||||||
|
36
pkg/query-service/model/errors.go
Normal file
36
pkg/query-service/model/errors.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// custom errors related to registration
|
||||||
|
type ErrFeatureUnavailable struct {
|
||||||
|
Key string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (errFeatureUnavailable ErrFeatureUnavailable) Error() string {
|
||||||
|
return fmt.Sprintf("feature unavailable: %s", errFeatureUnavailable.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrEmailRequired struct{}
|
||||||
|
|
||||||
|
func (errEmailRequired ErrEmailRequired) Error() string {
|
||||||
|
return "email is required"
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrPasswordRequired struct{}
|
||||||
|
|
||||||
|
func (errPasswordRequired ErrPasswordRequired) Error() string {
|
||||||
|
return "password is required"
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrSignupFailed struct{}
|
||||||
|
|
||||||
|
func (errSignupFailed ErrSignupFailed) Error() string {
|
||||||
|
return "failed to register user"
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrNoOrgFound struct{}
|
||||||
|
|
||||||
|
func (errNoOrgFound ErrNoOrgFound) Error() string {
|
||||||
|
return "no org found"
|
||||||
|
}
|
9
pkg/query-service/model/featureSet.go
Normal file
9
pkg/query-service/model/featureSet.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type FeatureSet map[string]bool
|
||||||
|
|
||||||
|
const Basic = "BASIC_PLAN"
|
||||||
|
|
||||||
|
var BasicPlan = FeatureSet{
|
||||||
|
Basic: true,
|
||||||
|
}
|
@ -12,10 +12,40 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type BaseApiError interface {
|
||||||
|
Type() ErrorType
|
||||||
|
ToError() error
|
||||||
|
Error() string
|
||||||
|
IsNil() bool
|
||||||
|
}
|
||||||
|
|
||||||
type ApiError struct {
|
type ApiError struct {
|
||||||
Typ ErrorType
|
Typ ErrorType
|
||||||
Err error
|
Err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *ApiError) Type() ErrorType {
|
||||||
|
return a.Typ
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiError) ToError() error {
|
||||||
|
if a != nil {
|
||||||
|
return a.Err
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiError) Error() string {
|
||||||
|
if a == nil || a.Err == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return a.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiError) IsNil() bool {
|
||||||
|
return a == nil || a.Err == nil
|
||||||
|
}
|
||||||
|
|
||||||
type ErrorType string
|
type ErrorType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -34,6 +64,22 @@ const (
|
|||||||
ErrorStreamingNotSupported ErrorType = "streaming is not supported"
|
ErrorStreamingNotSupported ErrorType = "streaming is not supported"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// BadRequest returns a ApiError object of bad request
|
||||||
|
func BadRequest(err error) *ApiError {
|
||||||
|
return &ApiError{
|
||||||
|
Typ: ErrorBadData,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InternalError returns a ApiError object of internal type
|
||||||
|
func InternalError(err error) *ApiError {
|
||||||
|
return &ApiError{
|
||||||
|
Typ: ErrorInternal,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type QueryDataV2 struct {
|
type QueryDataV2 struct {
|
||||||
ResultType promql.ValueType `json:"resultType"`
|
ResultType promql.ValueType `json:"resultType"`
|
||||||
Result promql.Value `json:"result"`
|
Result promql.Value `json:"result"`
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
pql "github.com/prometheus/prometheus/promql"
|
pql "github.com/prometheus/prometheus/promql"
|
||||||
pstorage "github.com/prometheus/prometheus/storage"
|
pstorage "github.com/prometheus/prometheus/storage"
|
||||||
premote "github.com/prometheus/prometheus/storage/remote"
|
premote "github.com/prometheus/prometheus/storage/remote"
|
||||||
"go.signoz.io/query-service/interfaces"
|
"go.signoz.io/signoz/pkg/query-service/interfaces"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PqlEngine struct {
|
type PqlEngine struct {
|
||||||
|
@ -3,12 +3,13 @@ package rules
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"go.signoz.io/query-service/model"
|
|
||||||
"go.signoz.io/query-service/utils/labels"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/utils/labels"
|
||||||
)
|
)
|
||||||
|
|
||||||
// this file contains common structs and methods used by
|
// this file contains common structs and methods used by
|
||||||
|
@ -4,14 +4,15 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"go.signoz.io/query-service/model"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"go.signoz.io/query-service/utils/times"
|
"github.com/pkg/errors"
|
||||||
"go.signoz.io/query-service/utils/timestamp"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/utils/times"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/utils/timestamp"
|
||||||
yaml "gopkg.in/yaml.v2"
|
yaml "gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -4,13 +4,14 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/google/uuid"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"github.com/go-kit/log"
|
"github.com/go-kit/log"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
@ -19,9 +20,9 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
// opentracing "github.com/opentracing/opentracing-go"
|
// opentracing "github.com/opentracing/opentracing-go"
|
||||||
am "go.signoz.io/query-service/integrations/alertManager"
|
am "go.signoz.io/signoz/pkg/query-service/integrations/alertManager"
|
||||||
"go.signoz.io/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
"go.signoz.io/query-service/utils/labels"
|
"go.signoz.io/signoz/pkg/query-service/utils/labels"
|
||||||
)
|
)
|
||||||
|
|
||||||
// namespace for prom metrics
|
// namespace for prom metrics
|
||||||
|
@ -1,155 +0,0 @@
|
|||||||
package rules
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
|
||||||
"go.signoz.io/query-service/app/clickhouseReader"
|
|
||||||
am "go.signoz.io/query-service/integrations/alertManager"
|
|
||||||
"go.signoz.io/query-service/model"
|
|
||||||
pqle "go.signoz.io/query-service/pqlEngine"
|
|
||||||
"go.signoz.io/query-service/utils/value"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"go.uber.org/zap/zapcore"
|
|
||||||
"net/url"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func initZapLog() *zap.Logger {
|
|
||||||
config := zap.NewDevelopmentConfig()
|
|
||||||
config.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
|
|
||||||
config.EncoderConfig.TimeKey = "timestamp"
|
|
||||||
config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
|
||||||
logger, _ := config.Build()
|
|
||||||
return logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRules(t *testing.T) {
|
|
||||||
fmt.Println("starting test TestRules..")
|
|
||||||
loggerMgr := initZapLog()
|
|
||||||
zap.ReplaceGlobals(loggerMgr)
|
|
||||||
defer loggerMgr.Sync() // flushes buffer, if any
|
|
||||||
|
|
||||||
logger := loggerMgr.Sugar()
|
|
||||||
|
|
||||||
configFile := "../config/prometheus.yml"
|
|
||||||
// create engine
|
|
||||||
pqle, err := pqle.FromConfigPath(configFile)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("failed to create pql:", err)
|
|
||||||
t.Errorf("failed to create pql engine : %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create db conn
|
|
||||||
db, err := sqlx.Open("sqlite3", "../signoz.db")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("failed to create db conn:", err)
|
|
||||||
t.Errorf("failed to create db conn: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create ch reader
|
|
||||||
ch := clickhouseReader.NewReader(db, configFile)
|
|
||||||
|
|
||||||
// notifier opts
|
|
||||||
notifierOpts := am.NotifierOptions{
|
|
||||||
QueueCapacity: 10000,
|
|
||||||
Timeout: 1 * time.Second,
|
|
||||||
AlertManagerURLs: []string{"http://localhost:9093/api/"},
|
|
||||||
}
|
|
||||||
|
|
||||||
externalURL, _ := url.Parse("http://signoz.io")
|
|
||||||
|
|
||||||
// create manager opts
|
|
||||||
managerOpts := &ManagerOptions{
|
|
||||||
NotifierOpts: notifierOpts,
|
|
||||||
Queriers: &Queriers{
|
|
||||||
PqlEngine: pqle,
|
|
||||||
Ch: ch,
|
|
||||||
},
|
|
||||||
ExternalURL: externalURL,
|
|
||||||
Conn: db,
|
|
||||||
Context: context.Background(),
|
|
||||||
Logger: nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
// create Manager
|
|
||||||
manager, err := NewManager(managerOpts)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("manager error:", err)
|
|
||||||
t.Errorf("manager error: %v", err)
|
|
||||||
}
|
|
||||||
fmt.Println("manager is ready:", manager)
|
|
||||||
|
|
||||||
manager.run()
|
|
||||||
|
|
||||||
// test rules
|
|
||||||
// create promql rule
|
|
||||||
/* promql rule
|
|
||||||
postableRule := PostableRule{
|
|
||||||
Alert: "test alert 1 - promql",
|
|
||||||
RuleType: RuleTypeProm,
|
|
||||||
EvalWindow: 5 * time.Minute,
|
|
||||||
Frequency: 30 * time.Second,
|
|
||||||
RuleCondition: RuleCondition{
|
|
||||||
CompositeMetricQuery: &model.CompositeMetricQuery{
|
|
||||||
QueryType: model.PROM,
|
|
||||||
PromQueries: map[string]*model.PromQuery{
|
|
||||||
"A": &model.PromQuery{Query: `sum(signoz_latency_count{span_kind="SPAN_KIND_SERVER"}) by (service_name) > 100`},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Labels: map[string]string{},
|
|
||||||
Annotations: map[string]string{},
|
|
||||||
}*/
|
|
||||||
// create builder rule
|
|
||||||
metricQuery := &model.MetricQuery{
|
|
||||||
QueryName: "A",
|
|
||||||
MetricName: "signoz_latency_count",
|
|
||||||
TagFilters: &model.FilterSet{Operation: "AND", Items: []model.FilterItem{
|
|
||||||
{Key: "span_kind", Value: "SPAN_KIND_SERVER", Operation: "neq"},
|
|
||||||
}},
|
|
||||||
GroupingTags: []string{"service_name"},
|
|
||||||
AggregateOperator: model.RATE_SUM,
|
|
||||||
Expression: "A",
|
|
||||||
}
|
|
||||||
|
|
||||||
postableRule := PostableRule{
|
|
||||||
Alert: "test alert 2 - builder",
|
|
||||||
RuleType: RuleTypeThreshold,
|
|
||||||
EvalWindow: 5 * time.Minute,
|
|
||||||
Frequency: 30 * time.Second,
|
|
||||||
RuleCondition: RuleCondition{
|
|
||||||
Target: value.Float64(500),
|
|
||||||
CompareOp: TargetIsMore,
|
|
||||||
CompositeMetricQuery: &model.CompositeMetricQuery{
|
|
||||||
QueryType: model.QUERY_BUILDER,
|
|
||||||
BuilderQueries: map[string]*model.MetricQuery{
|
|
||||||
"A": metricQuery,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Labels: map[string]string{"host": "server1"},
|
|
||||||
Annotations: map[string]string{},
|
|
||||||
}
|
|
||||||
err = manager.addTask(&postableRule, postableRule.Alert)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("failed to add rule: ", err)
|
|
||||||
t.Errorf("failed to add rule")
|
|
||||||
}
|
|
||||||
|
|
||||||
signalsChannel := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(signalsChannel, os.Interrupt, syscall.SIGTERM)
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-signalsChannel:
|
|
||||||
logger.Fatal("Received OS Interrupt Signal ... ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,18 +3,19 @@ package rules
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/go-kit/log"
|
|
||||||
"github.com/go-kit/log/level"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-kit/log"
|
||||||
|
"github.com/go-kit/log/level"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
plabels "github.com/prometheus/prometheus/pkg/labels"
|
plabels "github.com/prometheus/prometheus/pkg/labels"
|
||||||
pql "github.com/prometheus/prometheus/promql"
|
pql "github.com/prometheus/prometheus/promql"
|
||||||
"go.signoz.io/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
qslabels "go.signoz.io/query-service/utils/labels"
|
qslabels "go.signoz.io/signoz/pkg/query-service/utils/labels"
|
||||||
"go.signoz.io/query-service/utils/times"
|
"go.signoz.io/signoz/pkg/query-service/utils/times"
|
||||||
"go.signoz.io/query-service/utils/timestamp"
|
"go.signoz.io/signoz/pkg/query-service/utils/timestamp"
|
||||||
yaml "gopkg.in/yaml.v2"
|
yaml "gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ package rules
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/ClickHouse/clickhouse-go/v2"
|
"github.com/ClickHouse/clickhouse-go/v2"
|
||||||
pqle "go.signoz.io/query-service/pqlEngine"
|
pqle "go.signoz.io/signoz/pkg/query-service/pqlEngine"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Queriers register the options for querying metrics or event sources
|
// Queriers register the options for querying metrics or event sources
|
||||||
|
@ -5,7 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"go.signoz.io/query-service/utils/labels"
|
"go.signoz.io/signoz/pkg/query-service/utils/labels"
|
||||||
)
|
)
|
||||||
|
|
||||||
// common result format of query
|
// common result format of query
|
||||||
|
@ -2,8 +2,9 @@ package rules
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"go.signoz.io/query-service/utils/labels"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/utils/labels"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A Rule encapsulates a vector expression which is evaluated at a specified
|
// A Rule encapsulates a vector expression which is evaluated at a specified
|
||||||
|
@ -3,12 +3,13 @@ package rules
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
opentracing "github.com/opentracing/opentracing-go"
|
|
||||||
"go.signoz.io/query-service/utils/labels"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
opentracing "github.com/opentracing/opentracing-go"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/utils/labels"
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RuleTask holds a rule (with composite queries)
|
// RuleTask holds a rule (with composite queries)
|
||||||
|
@ -14,7 +14,7 @@ import (
|
|||||||
html_template "html/template"
|
html_template "html/template"
|
||||||
text_template "text/template"
|
text_template "text/template"
|
||||||
|
|
||||||
"go.signoz.io/query-service/utils/times"
|
"go.signoz.io/signoz/pkg/query-service/utils/times"
|
||||||
)
|
)
|
||||||
|
|
||||||
// this file contains all the methods and structs
|
// this file contains all the methods and structs
|
||||||
|
@ -3,21 +3,22 @@ package rules
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go.uber.org/zap"
|
|
||||||
"math"
|
"math"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/ClickHouse/clickhouse-go/v2"
|
"github.com/ClickHouse/clickhouse-go/v2"
|
||||||
"go.signoz.io/query-service/app/metrics"
|
"go.signoz.io/signoz/pkg/query-service/app/metrics"
|
||||||
"go.signoz.io/query-service/constants"
|
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
qsmodel "go.signoz.io/query-service/model"
|
qsmodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||||
"go.signoz.io/query-service/utils/labels"
|
"go.signoz.io/signoz/pkg/query-service/utils/labels"
|
||||||
"go.signoz.io/query-service/utils/times"
|
"go.signoz.io/signoz/pkg/query-service/utils/times"
|
||||||
"go.signoz.io/query-service/utils/timestamp"
|
"go.signoz.io/signoz/pkg/query-service/utils/timestamp"
|
||||||
"go.signoz.io/query-service/utils/value"
|
"go.signoz.io/signoz/pkg/query-service/utils/value"
|
||||||
|
|
||||||
yaml "gopkg.in/yaml.v2"
|
yaml "gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
@ -11,10 +11,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
ph "github.com/posthog/posthog-go"
|
ph "github.com/posthog/posthog-go"
|
||||||
"go.signoz.io/query-service/constants"
|
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
"go.signoz.io/query-service/interfaces"
|
"go.signoz.io/signoz/pkg/query-service/interfaces"
|
||||||
"go.signoz.io/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
"go.signoz.io/query-service/version"
|
"go.signoz.io/signoz/pkg/query-service/version"
|
||||||
"gopkg.in/segmentio/analytics-go.v3"
|
"gopkg.in/segmentio/analytics-go.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -27,6 +27,9 @@ const (
|
|||||||
TELEMETRY_EVENT_HEART_BEAT = "Heart Beat"
|
TELEMETRY_EVENT_HEART_BEAT = "Heart Beat"
|
||||||
TELEMETRY_EVENT_ORG_SETTINGS = "Org Settings"
|
TELEMETRY_EVENT_ORG_SETTINGS = "Org Settings"
|
||||||
DEFAULT_SAMPLING = 0.1
|
DEFAULT_SAMPLING = 0.1
|
||||||
|
TELEMETRY_LICENSE_CHECK_FAILED = "License Check Failed"
|
||||||
|
TELEMETRY_LICENSE_UPDATED = "License Updated"
|
||||||
|
TELEMETRY_LICENSE_ACT_FAILED = "License Activation Failed"
|
||||||
)
|
)
|
||||||
|
|
||||||
const api_key = "4Gmoa4ixJAUHx2BpJxsjwA1bEfnwEeRz"
|
const api_key = "4Gmoa4ixJAUHx2BpJxsjwA1bEfnwEeRz"
|
||||||
|
@ -9,8 +9,8 @@ import (
|
|||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.signoz.io/query-service/auth"
|
"go.signoz.io/signoz/pkg/query-service/auth"
|
||||||
"go.signoz.io/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func invite(t *testing.T, email string) *model.InviteResponse {
|
func invite(t *testing.T, email string) *model.InviteResponse {
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.signoz.io/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
45
pkg/query-service/utils/encryption/encryption.go
Normal file
45
pkg/query-service/utils/encryption/encryption.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package encryption
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Encrypt(key, text []byte) ([]byte, error) {
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b := base64.StdEncoding.EncodeToString(text)
|
||||||
|
ciphertext := make([]byte, aes.BlockSize+len(b))
|
||||||
|
iv := ciphertext[:aes.BlockSize]
|
||||||
|
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cfb := cipher.NewCFBEncrypter(block, iv)
|
||||||
|
cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(b))
|
||||||
|
return ciphertext, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Decrypt(key, text []byte) ([]byte, error) {
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(text) < aes.BlockSize {
|
||||||
|
return nil, errors.New("ciphertext too short")
|
||||||
|
}
|
||||||
|
iv := text[:aes.BlockSize]
|
||||||
|
text = text[aes.BlockSize:]
|
||||||
|
cfb := cipher.NewCFBDecrypter(block, iv)
|
||||||
|
cfb.XORKeyStream(text, text)
|
||||||
|
data, err := base64.StdEncoding.DecodeString(string(text))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
|
}
|
10
pkg/query-service/utils/pass.go
Normal file
10
pkg/query-service/utils/pass.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sethvargo/go-password/password"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GeneratePassowrd() string {
|
||||||
|
res, _ := password.Generate(64, 10, 10, false, false)
|
||||||
|
return res
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user