Skip to content

Commit

Permalink
feat(usage): make measurement ID and api secret configurable via envs
Browse files Browse the repository at this point in the history
Signed-off-by: Niladri Halder <niladri.halder26@gmail.com>
  • Loading branch information
niladrih committed Aug 22, 2024
1 parent 17f9b1f commit 7cd2865
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 25 deletions.
4 changes: 2 additions & 2 deletions pkg/client/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import (
"github.com/openebs/lib-csi/pkg/common/errors"
)

var measurementIDMatcher = regexp.MustCompile(`^G-[a-zA-Z0-9]+$`)
var MeasurementIDMatcher = regexp.MustCompile(`^G-[a-zA-Z0-9]+$`)

type MeasurementClientOption func(*MeasurementClient) error

Expand Down Expand Up @@ -82,7 +82,7 @@ func WithMeasurementId(measurementId string) MeasurementClientOption {
return errors.Errorf("failed to set measurement_id: id is an empty string")
}

if !measurementIDMatcher.MatchString(measurementId) {
if !MeasurementIDMatcher.MatchString(measurementId) {
return errors.Errorf("Invalid measurement_id: %s", measurementId)
}

Expand Down
13 changes: 9 additions & 4 deletions usage/const.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package usage

const (
// MeasurementId is the unique code of the OpenEBS property in Google Analytics.
MeasurementId string = "G-TZGP46618W"
// ApiSecret is the measurement protocol api_secret.
ApiSecret string = "91JGdTg9QwGn7Y-vvuM4zA"
// DefaultMeasurementId is the default unique code of the OpenEBS property in Google Analytics.
DefaultMeasurementId string = "G-TZGP46618W"
// DefaultApiSecret is the default measurement protocol api_secret.
DefaultApiSecret string = "91JGdTg9QwGn7Y-vvuM4zA"

// InstallEvent event is sent on pod starts
InstallEvent string = "install"
Expand All @@ -26,4 +26,9 @@ const (
EventLabelNode string = "nodes"
// EventLabelCapacity holds the string label "capacity"
EventLabelCapacity string = "capacity"

// MeasurementIdEnv sets the measurement ID for the target GA4 property.
MeasurementIdEnv = "GA_ID"
// ApiSecretEnv sets the measurement protocol API secret for the target GA4 property.
ApiSecretEnv = "GA_KEY"
)
58 changes: 39 additions & 19 deletions usage/usage.go
Original file line number Diff line number Diff line change
@@ -1,31 +1,49 @@
/*
Copyright 2023 The OpenEBS Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package usage

import (
"encoding/base64"
"os"
"strconv"

k8sapi "github.com/openebs/lib-csi/pkg/client/k8s"
"k8s.io/klog/v2"

ga4Client "github.com/openebs/google-analytics-4/pkg/client"
ga4Event "github.com/openebs/google-analytics-4/pkg/event"
k8sapi "github.com/openebs/lib-csi/pkg/client/k8s"
)

// apiCreds reads envs and decodes base64 input for GA-4 MeasurementId and ApiSecret.
// Returns defaults if envs are unset/invalid.
func apiCreds() (string, string) {
encodedId, okId := os.LookupEnv(MeasurementIdEnv)
encodedSecret, okSecret := os.LookupEnv(ApiSecretEnv)

// Use defaults if envs are unset or they are set and value(s) are empty.
if !okId || !okSecret || (len(encodedId) == 0) || (len(encodedSecret) == 0) {
return DefaultMeasurementId, DefaultApiSecret
}

// Use defaults if the envs are not valid base64 strings.
id, errId := base64.StdEncoding.DecodeString(encodedId)
if errId != nil {
klog.Errorf("Failed to decode measurement id: %s", errId.Error())
return DefaultMeasurementId, DefaultApiSecret
}
secret, errSecret := base64.StdEncoding.DecodeString(encodedSecret)
if errSecret != nil {
klog.Errorf("Failed to decode secret: %s", errSecret.Error())
return DefaultMeasurementId, DefaultApiSecret
}

// Use defaults if the input measurement ID doesn't match the regex.
if !ga4Client.MeasurementIDMatcher.Match(id) {
klog.Errorf("Measurement ID does not match regex")
return DefaultMeasurementId, DefaultApiSecret
}

return string(id), string(secret)
}

// Usage struct represents all information about a usage metric sent to
// Google Analytics with respect to the application
type Usage struct {
Expand All @@ -38,9 +56,11 @@ type Usage struct {

// New returns an instance of Usage
func New() *Usage {
measurementId, apiSecret := apiCreds()

client, err := ga4Client.NewMeasurementClient(
ga4Client.WithApiSecret(ApiSecret),
ga4Client.WithMeasurementId(MeasurementId),
ga4Client.WithApiSecret(apiSecret),
ga4Client.WithMeasurementId(measurementId),
)
if err != nil {
return nil
Expand Down
132 changes: 132 additions & 0 deletions usage/usage_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package usage

import (
"encoding/base64"
"os"
"reflect"
"testing"
"unicode/utf8"
)

func TestApiCreds(t *testing.T) {
testCases := map[string]struct {
idEnvValue string
idSet bool
secretEnvValue string
secretSet bool
expectedId string
expectedSecret string
}{
"Missing both ENVs": {
idEnvValue: "",
idSet: false,
secretEnvValue: "",
secretSet: false,
expectedId: DefaultMeasurementId,
expectedSecret: DefaultApiSecret,
},
"Missing id ENV": {
idEnvValue: "",
idSet: false,
secretEnvValue: base64.StdEncoding.EncodeToString([]byte("testSecret")),
secretSet: true,
expectedId: DefaultMeasurementId,
expectedSecret: DefaultApiSecret,
},
"Missing secret ENV": {
idEnvValue: base64.StdEncoding.EncodeToString([]byte("G-testId")),
idSet: true,
secretEnvValue: "",
secretSet: false,
expectedId: DefaultMeasurementId,
expectedSecret: DefaultApiSecret,
},
"Empty id ENV": {
idEnvValue: "",
idSet: true,
secretEnvValue: base64.StdEncoding.EncodeToString([]byte("testSecret")),
secretSet: true,
expectedId: DefaultMeasurementId,
expectedSecret: DefaultApiSecret,
},
"Empty secret ENV": {
idEnvValue: base64.StdEncoding.EncodeToString([]byte("G-testId")),
idSet: true,
secretEnvValue: "",
secretSet: true,
expectedId: DefaultMeasurementId,
expectedSecret: DefaultApiSecret,
},
"Invalid base64 value for id ENV": {
idEnvValue: trimLastChar(base64.StdEncoding.EncodeToString([]byte("G-testId"))),
idSet: true,
secretEnvValue: base64.StdEncoding.EncodeToString([]byte("testSecret")),
secretSet: true,
expectedId: DefaultMeasurementId,
expectedSecret: DefaultApiSecret,
},
"Invalid base64 value for secret ENV": {
idEnvValue: base64.StdEncoding.EncodeToString([]byte("testId")),
idSet: true,
secretEnvValue: trimLastChar(base64.StdEncoding.EncodeToString([]byte("testSecret"))),
secretSet: true,
expectedId: DefaultMeasurementId,
expectedSecret: DefaultApiSecret,
},
"Valid ENVs set": {
idEnvValue: base64.StdEncoding.EncodeToString([]byte("G-testId")),
idSet: true,
secretEnvValue: base64.StdEncoding.EncodeToString([]byte("testSecret")),
secretSet: true,
expectedId: "G-testId",
expectedSecret: "testSecret",
},
}

for k, v := range testCases {
k, v := k, v
t.Run(k, func(t *testing.T) {
if v.idSet {
if os.Setenv(MeasurementIdEnv, v.idEnvValue) != nil {
t.Errorf("failed to set env '%s' to '%s' for test case '%s'",
MeasurementIdEnv, v.idEnvValue, k,
)
}
} else {
if os.Unsetenv(MeasurementIdEnv) != nil {
t.Errorf("failed to unset env '%s' for test case '%s'", MeasurementIdEnv, k)
}
}
if v.secretSet {
if os.Setenv(ApiSecretEnv, v.secretEnvValue) != nil {
t.Errorf("failed to set env '%s' to '%s' for test case '%s'",
ApiSecretEnv, v.secretEnvValue, k,
)
}
} else {
if os.Unsetenv(ApiSecretEnv) != nil {
t.Errorf("failed to unset env '%s' for test case '%s'", ApiSecretEnv, k)
}
}
observedId, observedSecret := apiCreds()
if !reflect.DeepEqual(observedId, v.expectedId) {
t.Errorf("apiCreds() id mismatch: expected '%s', observed '%s'",
v.expectedId, observedId,
)
}
if !reflect.DeepEqual(observedSecret, v.expectedSecret) {
t.Errorf("apiCreds() secret mismatch: expected '%s', observed '%s'",
v.expectedSecret, observedSecret,
)
}
})
}
}

func trimLastChar(s string) string {
r, size := utf8.DecodeLastRuneInString(s)
if r == utf8.RuneError && (size == 0 || size == 1) {
size = 0
}
return s[:len(s)-size]
}

0 comments on commit 7cd2865

Please sign in to comment.