Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Wire.go
Original file line number Diff line number Diff line change
Expand Up @@ -666,7 +666,6 @@ func InitializeApp() (*App, error) {
telemetry.NewTelemetryEventClientImpl,
wire.Bind(new(telemetry.TelemetryEventClient), new(*telemetry.TelemetryEventClientImpl)),

telemetry.GetPosthogConfig,
router.NewBulkUpdateRouterImpl,
wire.Bind(new(router.BulkUpdateRouter), new(*router.BulkUpdateRouterImpl)),
restHandler.NewBulkUpdateRestHandlerImpl,
Expand Down
95 changes: 33 additions & 62 deletions client/telemetry/PosthogClient.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,101 +19,72 @@ package telemetry

import (
"encoding/base64"
"encoding/json"
"github.com/caarlos0/env"
"github.com/devtron-labs/devtron/util"
"github.com/patrickmn/go-cache"
"github.com/posthog/posthog-go"
"go.uber.org/zap"
"io/ioutil"
"net/http"
"time"
)

type PosthogClient struct {
Client posthog.Client
cache *cache.Cache
cfg *PosthogConfig
}

type PosthogConfig struct {
PosthogApiKey string `env:"POSTHOG_API_KEY" envDefault:""`
PosthogEndpoint string `env:"POSTHOG_ENDPOINT" envDefault:"https://app.posthog.com"`
SummaryCronExpr string `env:"SUMMARY_CRON_EXPR" envDefault:"0 0 * * *"` // Run once a day, midnight
HeartbeatCronExpr string `env:"HEARTBEAT_CRON_EXPR" envDefault:"0 0/6 * * *"` // Run every 6 hour
CacheExpiry int `env:"CACHE_EXPIRY" envDefault:"120"`
TelemetryApiKeyEndpoint string `env:"TELEMETRY_API_KEY_ENDPOINT" envDefault:"aHR0cHM6Ly90ZWxlbWV0cnkuZGV2dHJvbi5haS9kZXZ0cm9uL3RlbGVtZXRyeS9hcGlrZXk="`
PosthogEncodedApiKey string
}
var (
PosthogApiKey string = ""
PosthogEndpoint string = "https://app.posthog.com"
SummaryCronExpr string = "0 0 * * *" // Run once a day, midnight
HeartbeatCronExpr string = "0 0/6 * * *"
CacheExpiry int = 1440
PosthogEncodedApiKey string = ""
IsOptOut bool = false
)

func GetPosthogConfig() (*PosthogConfig, error) {
cfg := &PosthogConfig{}
err := env.Parse(cfg)
if err != nil {
return nil, err
}
return cfg, err
}
const (
TelemetryApiKeyEndpoint string = "aHR0cHM6Ly90ZWxlbWV0cnkuZGV2dHJvbi5haS9kZXZ0cm9uL3RlbGVtZXRyeS9hcGlrZXk="
TelemetryOptOutApiBaseUrl string = "aHR0cHM6Ly90ZWxlbWV0cnkuZGV2dHJvbi5haS9kZXZ0cm9uL3RlbGVtZXRyeS9vcHQtb3V0"
)

func NewPosthogClient(logger *zap.SugaredLogger) (*PosthogClient, error) {
cfg := &PosthogConfig{}
err := env.Parse(cfg)
if err != nil {
logger.Errorw("exception caught while parsing posthog config", "err", err)
//return &PosthogClient{}, err
}
if len(cfg.PosthogApiKey) == 0 {
encodedApiKey, apiKey, err := getPosthogApiKey(cfg.TelemetryApiKeyEndpoint)
if len(PosthogApiKey) == 0 {
encodedApiKey, apiKey, err := getPosthogApiKey(TelemetryApiKeyEndpoint, logger)
if err != nil {
logger.Errorw("exception caught while getting api key", "err", err)
//return &PosthogClient{}, err
}
cfg.PosthogApiKey = apiKey
cfg.PosthogEncodedApiKey = encodedApiKey
PosthogApiKey = apiKey
PosthogEncodedApiKey = encodedApiKey
}
client, _ := posthog.NewWithConfig(cfg.PosthogApiKey, posthog.Config{Endpoint: cfg.PosthogEndpoint})
client, err := posthog.NewWithConfig(PosthogApiKey, posthog.Config{Endpoint: PosthogEndpoint})
//defer client.Close()
d := time.Duration(cfg.CacheExpiry)
c := cache.New(d*time.Minute, 240*time.Minute)
if err != nil {
logger.Errorw("exception caught while creating posthog client", "err", err)
}
d := time.Duration(CacheExpiry)
c := cache.New(d*time.Minute, d*time.Minute)
pgClient := &PosthogClient{
Client: client,
cache: c,
cfg: cfg,
}
return pgClient, nil
}

func getPosthogApiKey(encodedPosthogApiKeyUrl string) (string, string, error) {
dncodedPosthogApiKeyUrl, err := base64.StdEncoding.DecodeString(encodedPosthogApiKeyUrl)
func getPosthogApiKey(encodedPosthogApiKeyUrl string, logger *zap.SugaredLogger) (string, string, error) {
decodedPosthogApiKeyUrl, err := base64.StdEncoding.DecodeString(encodedPosthogApiKeyUrl)
if err != nil {
logger.Errorw("error fetching posthog api key, decode error", "err", err)
return "", "", err
}
apiKeyUrl := string(dncodedPosthogApiKeyUrl)
req, err := http.NewRequest(http.MethodGet, apiKeyUrl, nil)
apiKeyUrl := string(decodedPosthogApiKeyUrl)
response, err := util.HttpRequest(apiKeyUrl)
if err != nil {
logger.Errorw("error fetching posthog api key, http call", "err", err)
return "", "", err
}
//var client *http.Client
client := &http.Client{}
res, err := client.Do(req)
encodedApiKey := response["result"].(string)
apiKey, err := base64.StdEncoding.DecodeString(encodedApiKey)
if err != nil {
return "", "", err
}
if res.StatusCode >= 200 && res.StatusCode <= 299 {
resBody, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", "", err
}
var apiRes map[string]interface{}
err = json.Unmarshal(resBody, &apiRes)
if err != nil {
return "", "", err
}
encodedApiKey := apiRes["result"].(string)
apiKey, err := base64.StdEncoding.DecodeString(encodedApiKey)
if err != nil {
return "", "", err
}
return encodedApiKey, string(apiKey), err
}
return "", "", err
return encodedApiKey, string(apiKey), err
}
77 changes: 67 additions & 10 deletions client/telemetry/TelemetryEventClient.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package telemetry

import (
"encoding/base64"
"encoding/json"
"fmt"
"github.com/devtron-labs/devtron/internal/sql/repository"
Expand Down Expand Up @@ -35,7 +36,6 @@ type TelemetryEventClientImpl struct {
PosthogClient *PosthogClient
ciPipelineRepository pipelineConfig.CiPipelineRepository
pipelineRepository pipelineConfig.PipelineRepository
posthogConfig *PosthogConfig
}

type TelemetryEventClient interface {
Expand All @@ -46,8 +46,7 @@ func NewTelemetryEventClientImpl(logger *zap.SugaredLogger, client *http.Client,
K8sUtil *util2.K8sUtil, aCDAuthConfig *user.ACDAuthConfig,
environmentService cluster.EnvironmentService, userService user.UserService,
appListingRepository repository.AppListingRepository, PosthogClient *PosthogClient,
ciPipelineRepository pipelineConfig.CiPipelineRepository, pipelineRepository pipelineConfig.PipelineRepository,
posthogConfig *PosthogConfig) (*TelemetryEventClientImpl, error) {
ciPipelineRepository pipelineConfig.CiPipelineRepository, pipelineRepository pipelineConfig.PipelineRepository, ) (*TelemetryEventClientImpl, error) {
cron := cron.New(
cron.WithChain())
cron.Start()
Expand All @@ -59,19 +58,18 @@ func NewTelemetryEventClientImpl(logger *zap.SugaredLogger, client *http.Client,
environmentService: environmentService, userService: userService,
appListingRepository: appListingRepository, PosthogClient: PosthogClient,
ciPipelineRepository: ciPipelineRepository, pipelineRepository: pipelineRepository,
posthogConfig: posthogConfig,
}

watcher.HeartbeatEventForTelemetry()
_, err := cron.AddFunc(watcher.posthogConfig.SummaryCronExpr, watcher.SummaryEventForTelemetry)
_, err := cron.AddFunc(SummaryCronExpr, watcher.SummaryEventForTelemetry)
if err != nil {
fmt.Println("error in starting summery event", "err", err)
logger.Errorw("error in starting summery event", "err", err)
return nil, err
}

_, err = cron.AddFunc(watcher.posthogConfig.HeartbeatCronExpr, watcher.HeartbeatEventForTelemetry)
_, err = cron.AddFunc(HeartbeatCronExpr, watcher.HeartbeatEventForTelemetry)
if err != nil {
fmt.Println("error in starting heartbeat event", "err", err)
logger.Errorw("error in starting heartbeat event", "err", err)
return nil, err
}
return watcher, err
Expand Down Expand Up @@ -131,6 +129,12 @@ func (impl *TelemetryEventClientImpl) SummaryEventForTelemetry() {
impl.logger.Errorw("exception caught inside telemetry summary event", "err", err)
return
}

if IsOptOut {
impl.logger.Warnw("client is opt-out for telemetry, there will be no events capture", "ucid", ucid)
return
}

discoveryClient, err := impl.K8sUtil.GetK8sDiscoveryClientInCluster()
if err != nil {
impl.logger.Errorw("exception caught inside telemetry summary event", "err", err)
Expand Down Expand Up @@ -207,6 +211,13 @@ func (impl *TelemetryEventClientImpl) SummaryEventForTelemetry() {
return
}

if impl.PosthogClient.Client == nil {
impl.logger.Warn("no posthog client found, creating new")
client, err := impl.retryPosthogClient(PosthogApiKey, PosthogEndpoint)
if err == nil {
impl.PosthogClient.Client = client
}
}
if impl.PosthogClient.Client != nil {
err = impl.PosthogClient.Client.Enqueue(posthog.Capture{
DistinctId: ucid,
Expand All @@ -225,6 +236,11 @@ func (impl *TelemetryEventClientImpl) HeartbeatEventForTelemetry() {
impl.logger.Errorw("exception caught inside telemetry heartbeat event", "err", err)
return
}
if IsOptOut {
impl.logger.Warnw("client is opt-out for telemetry, there will be no events capture", "ucid", ucid)
return
}

discoveryClient, err := impl.K8sUtil.GetK8sDiscoveryClientInCluster()
if err != nil {
impl.logger.Errorw("exception caught inside telemetry heartbeat event", "err", err)
Expand All @@ -248,6 +264,14 @@ func (impl *TelemetryEventClientImpl) HeartbeatEventForTelemetry() {
impl.logger.Errorw("HeartbeatEventForTelemetry, payload unmarshal error", "error", err)
return
}

if impl.PosthogClient.Client == nil {
impl.logger.Warn("no posthog client found, creating new")
client, err := impl.retryPosthogClient(PosthogApiKey, PosthogEndpoint)
if err == nil {
impl.PosthogClient.Client = client
}
}
if impl.PosthogClient.Client != nil {
err = impl.PosthogClient.Client.Enqueue(posthog.Capture{
DistinctId: ucid,
Expand All @@ -267,9 +291,9 @@ func (impl *TelemetryEventClientImpl) GetTelemetryMetaInfo() (*TelemetryMetaInfo
return nil, err
}
data := &TelemetryMetaInfo{
Url: impl.posthogConfig.PosthogEndpoint,
Url: PosthogEndpoint,
UCID: ucid,
ApiKey: impl.PosthogClient.cfg.PosthogEncodedApiKey,
ApiKey: PosthogEncodedApiKey,
}
return data, err
}
Expand Down Expand Up @@ -312,6 +336,39 @@ func (impl *TelemetryEventClientImpl) getUCID() (string, error) {
impl.logger.Errorw("configmap not found while getting unique client id", "cm", cm)
return ucid.(string), err
}
flag, err := impl.checkForOptOut(ucid.(string))
if err != nil {
impl.logger.Errorw("error sending event to posthog, failed check for opt-out", "err", err)
return "", err
}
IsOptOut = flag
}
return ucid.(string), nil
}

func (impl *TelemetryEventClientImpl) checkForOptOut(UCID string) (bool, error) {
decodedUrl, err := base64.StdEncoding.DecodeString(TelemetryOptOutApiBaseUrl)
if err != nil {
impl.logger.Errorw("check opt-out list failed, decode error", "err", err)
return false, err
}
encodedUrl := string(decodedUrl)
url := fmt.Sprintf("%s/%s", encodedUrl, UCID)

response, err := util.HttpRequest(url)
if err != nil {
impl.logger.Errorw("check opt-out list failed, rest api error", "err", err)
return false, err
}
flag := response["result"].(bool)
return flag, err
}

func (impl *TelemetryEventClientImpl) retryPosthogClient(PosthogApiKey string, PosthogEndpoint string) (posthog.Client, error) {
client, err := posthog.NewWithConfig(PosthogApiKey, posthog.Config{Endpoint: PosthogEndpoint})
//defer client.Close()
if err != nil {
impl.logger.Errorw("exception caught while creating posthog client", "err", err)
}
return client, err
}
5 changes: 1 addition & 4 deletions pkg/sso/SSOLoginService.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"github.com/argoproj/argo-cd/util/session"
"github.com/devtron-labs/devtron/api/bean"
session2 "github.com/devtron-labs/devtron/client/argocdServer/session"
"github.com/devtron-labs/devtron/client/telemetry"
"github.com/devtron-labs/devtron/internal/sql/repository"
"github.com/devtron-labs/devtron/internal/util"
"github.com/devtron-labs/devtron/pkg/cluster"
Expand Down Expand Up @@ -54,14 +53,13 @@ type SSOLoginServiceImpl struct {
clusterService cluster.ClusterService
envService cluster.EnvironmentService
aCDAuthConfig *user.ACDAuthConfig
posthogConfig *telemetry.PosthogConfig
}

func NewSSOLoginServiceImpl(userAuthRepository repository.UserAuthRepository, sessionManager *session.SessionManager,
client session2.ServiceClient, logger *zap.SugaredLogger, userRepository repository.UserRepository,
userGroupRepository repository.RoleGroupRepository, ssoLoginRepository repository.SSOLoginRepository,
K8sUtil *util.K8sUtil, clusterService cluster.ClusterService, envService cluster.EnvironmentService,
aCDAuthConfig *user.ACDAuthConfig, posthogConfig *telemetry.PosthogConfig) *SSOLoginServiceImpl {
aCDAuthConfig *user.ACDAuthConfig) *SSOLoginServiceImpl {
serviceImpl := &SSOLoginServiceImpl{
userAuthRepository: userAuthRepository,
sessionManager: sessionManager,
Expand All @@ -74,7 +72,6 @@ func NewSSOLoginServiceImpl(userAuthRepository repository.UserAuthRepository, se
clusterService: clusterService,
envService: envService,
aCDAuthConfig: aCDAuthConfig,
posthogConfig: posthogConfig,
}
return serviceImpl
}
Expand Down
31 changes: 31 additions & 0 deletions util/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,15 @@
package util

import (
"encoding/json"
"fmt"
"go.uber.org/zap"
"io/ioutil"
"math/rand"
"net/http"
"strconv"
"strings"
"time"
)

func ContainsString(list []string, element string) bool {
Expand Down Expand Up @@ -80,10 +84,37 @@ func Close(c Closer, logger *zap.SugaredLogger) {
var chars = []rune("abcdefghijklmnopqrstuvwxyz0123456789")

func Generate(size int) string {
rand.Seed(time.Now().UnixNano())
var b strings.Builder
for i := 0; i < size; i++ {
b.WriteRune(chars[rand.Intn(len(chars))])
}
str := b.String()
return str
}

func HttpRequest(url string) (map[string]interface{}, error) {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}
//var client *http.Client
client := &http.Client{}
res, err := client.Do(req)
if err != nil {
return nil, err
}
if res.StatusCode >= 200 && res.StatusCode <= 299 {
resBody, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
var apiRes map[string]interface{}
err = json.Unmarshal(resBody, &apiRes)
if err != nil {
return nil, err
}
return apiRes, err
}
return nil, err
}
Loading