Skip to content

Commit

Permalink
feat: added json key input for the constructor
Browse files Browse the repository at this point in the history
  • Loading branch information
deejiw committed Jun 6, 2023
1 parent 477e9c5 commit fa572d5
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 33 deletions.
143 changes: 119 additions & 24 deletions module.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"os"
"reflect"

"github.com/dop251/goja"
"go.k6.io/k6/js/common"
Expand Down Expand Up @@ -32,28 +33,32 @@ type (
}

GcpConfig struct {
scope []string
Key ServiceAccountKey
Scope []string
}

Option func(*Gcp) error

ServiceAccountKey struct {
AuthProviderX509CertUrl string `json:"auth_provider_x509_cert_url"`
AuthURL string `json:"auth_uri"`
ClientEmail string `json:"client_email" validate:"required"`
ClientID string `json:"client_id" validate:"required"`
ClientSecret string `json:"client_secret" validate:"required"`
ClientEmail string `json:"client_email"`
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
ClientX509CertUrl string `json:"client_x509_cert_url"`
PrivateKey string `json:"private_key" validate:"required"`
PrivateKeyID string `json:"private_key_id" validate:"required"`
ProjectID string `json:"project_id" validate:"required"`
TokenURL string `json:"token_uri" validate:"required"`
Type string `json:"type" validate:"required"`
PrivateKey string `json:"private_key"`
PrivateKeyID string `json:"private_key_id"`
ProjectID string `json:"project_id"`
TokenURL string `json:"token_uri"`
Type string `json:"type"`
UniverseDomain string `json:"universe_domain"`
}
)

var (
_ modules.Module = &RootModule{}
_ modules.Instance = &ModuleInstance{}
_ modules.Module = &RootModule{}
_ modules.Instance = &ModuleInstance{}
gcpConstructorDefaultScope = []string{"https://www.googleapis.com/auth/cloud-platform"}
)

func New() *RootModule {
Expand All @@ -77,35 +82,125 @@ func (mi *ModuleInstance) Exports() modules.Exports {
func (mi *ModuleInstance) newGcp(c goja.ConstructorCall) *goja.Object {
rt := mi.vu.Runtime()
const envVar = "GOOGLE_SERVICE_ACCOUNT_KEY"
var options GcpConfig

err := rt.ExportTo(c.Argument(0), &options)
if err != nil {
common.Throw(rt,
fmt.Errorf("gcp constructor fails to read options: %w", err))
}

// fmt.Printf("%+v\n", options.Key)
// fmt.Print(options.Scope)

if !isStructEmpty(options.Key) {
b, err := convertToByte(options.Key)
if err != nil {
common.Throw(rt, err)
}

g, err := newGcpConstructor(
withGcpConstructorKey(b),
withGcpConstructorScope(options.Scope),
)

if err != nil {
common.Throw(rt, fmt.Errorf("cannot initialize gcp constructor <%w>", err))
}

return rt.ToValue(g).ToObject(rt)
}

if keyString := os.Getenv(envVar); keyString != "" {
key := &ServiceAccountKey{}

err := json.Unmarshal([]byte(keyString), &key)
if err != nil {
common.Throw(rt, fmt.Errorf("Cannot unmarshal environment variable %v <%w>", envVar, err))
common.Throw(rt, fmt.Errorf("cannot unmarshal environment variable %v <%w>", envVar, err))
}

var options GcpConfig
err = rt.ExportTo(c.Argument(0), &options)
b, err := convertToByte(key)
if err != nil {
common.Throw(rt,
fmt.Errorf("Gcp constructor expects scope as it's argument: %w", err))
common.Throw(rt, err)
}

keyByte, _ := json.Marshal(key)
g, err := newGcpConstructor(
withGcpConstructorKey(b),
withGcpConstructorScope(options.Scope),
)

obj := &Gcp{
// vu: mi.vu,
keyByte: keyByte,
scope: options.scope,
if err != nil {
common.Throw(rt, fmt.Errorf("cannot initialize gcp constructor <%w>", err))
}

return rt.ToValue(obj).ToObject(rt)
return rt.ToValue(g).ToObject(rt)

}

common.Throw(rt, fmt.Errorf("environment variable %v not found", envVar))
common.Throw(rt, fmt.Errorf("service account key not found. Please use %s or input 'key' parameter", envVar))

return nil
}

func convertToByte(key interface{}) ([]byte, error) {
b, err := json.Marshal(key)

if err != nil {
return nil, fmt.Errorf("cannot unmarshal key <%w>", err)
}

return b, nil
}

// The function creates a new instance of the Gcp struct with specified options.
func newGcpConstructor(opts ...Option) (Gcp, error) {
g := Gcp{
scope: gcpConstructorDefaultScope,
}

for _, opt := range opts {
if err := opt(&g); err != nil {
return Gcp{}, fmt.Errorf("gcp constructor fails to read options %w", err)
}
}

return g, nil
}

func withGcpConstructorKey(b []byte) func(*Gcp) error {
return func(g *Gcp) error {
g.keyByte = b

return nil
}
}

func withGcpConstructorScope(scope []string) func(*Gcp) error {
return func(g *Gcp) error {
if len(scope) != 0 {
g.scope = scope
}

return nil
}
}

func isStructEmpty(object interface{}) bool {
// check normal definitions of empty
if object == nil {
return true
} else if object == "" {
return true
} else if object == false {
return true
}

// see if it's a struct
if reflect.ValueOf(object).Kind() == reflect.Struct {
// and create an empty copy of the struct object to compare against
empty := reflect.New(reflect.TypeOf(object)).Elem().Interface()
if reflect.DeepEqual(object, empty) {
return true
}
}
return false
}
25 changes: 20 additions & 5 deletions monitoring.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ import (

monitoring "cloud.google.com/go/monitoring/apiv3/v2"
"cloud.google.com/go/monitoring/apiv3/v2/monitoringpb"
"golang.org/x/oauth2"
"google.golang.org/api/iterator"
"google.golang.org/api/option"
)

// This function is querying time series data from Google Cloud Monitoring API. It takes in a project
// ID and a query string as parameters, and returns a slice of `monitoringpb.TimeSeriesData` and an
// error.
func (g *Gcp) QueryTimeSeries(projectId string, query string) ([]*monitoringpb.TimeSeriesData, error) {
ctx := context.Background()

Expand All @@ -18,14 +22,12 @@ func (g *Gcp) QueryTimeSeries(projectId string, query string) ([]*monitoringpb.T
return nil, err
}

c, err := monitoring.NewQueryClient(ctx, option.WithTokenSource(jwt.TokenSource(ctx)))
c, err := queryClient(ctx, jwt.TokenSource(ctx))

if err != nil {
return nil, fmt.Errorf("Could not initialize query client <%w>", err)
return nil, err
}

defer c.Close()

req := &monitoringpb.QueryTimeSeriesRequest{
Name: "projects/" + projectId,
Query: query,
Expand All @@ -41,10 +43,23 @@ func (g *Gcp) QueryTimeSeries(projectId string, query string) ([]*monitoringpb.T
break
}
if err != nil {
return nil, fmt.Errorf("Could not list time series: %w", err)
return nil, fmt.Errorf("could not list time series: %w", err)
}
result = append(result, resp)
}

defer c.Close()

return result, nil
}

// The function initializes a query client for Google Cloud Monitoring using a token source.
func queryClient(ctx context.Context, ts oauth2.TokenSource) (*monitoring.QueryClient, error) {
c, err := monitoring.NewQueryClient(ctx, option.WithTokenSource(ts))

if err != nil {
return nil, fmt.Errorf("could not initialize query client <%w>", err)
}

return c, nil
}
8 changes: 4 additions & 4 deletions oauth2.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func (g *Gcp) GetOAuth2AccessToken(scope []string) (*oauth2.Token, error) {

token, err := jwt.TokenSource(ctx).Token()
if err != nil {
return nil, fmt.Errorf("Failed to obtain Access Token from JWT config with scope %s <%w>", scope, err)
return nil, fmt.Errorf("failed to obtain Access Token from JWT config with scope %s <%w>", scope, err)
}

return token, nil
Expand All @@ -51,7 +51,7 @@ func (g *Gcp) GetOAuth2IdToken(scope []string) (*oauth2.Token, error) {

token, err := ts.Token()
if err != nil {
return nil, fmt.Errorf("Failed to obtain ID Token from JWT Token Source for scope %s <%w>", scope, err)
return nil, fmt.Errorf("failed to obtain ID Token from JWT Token Source for scope %s <%w>", scope, err)
}

return token, nil
Expand All @@ -61,7 +61,7 @@ func (g *Gcp) GetOAuth2IdToken(scope []string) (*oauth2.Token, error) {
func getJwtConfig(keyByte []byte, scope []string) (*jwt.Config, error) {
jwt, err := google.JWTConfigFromJSON(keyByte, scope...)
if err != nil {
return nil, fmt.Errorf("Failed to obtain JWT Config for scope %s <%w>", scope, err)
return nil, fmt.Errorf("failed to obtain JWT Config for scope %s <%w>", scope, err)
}

return jwt, nil
Expand All @@ -71,7 +71,7 @@ func getJwtConfig(keyByte []byte, scope []string) (*jwt.Config, error) {
func getTokenSource(keyByte []byte, scope []string) (oauth2.TokenSource, error) {
ts, err := google.JWTAccessTokenSourceWithScope(keyByte, scope...)
if err != nil {
return nil, fmt.Errorf("Failed to obtain JWT Token Source for scope %s <%w>", scope, err)
return nil, fmt.Errorf("failed to obtain JWT Token Source for scope %s <%w>", scope, err)
}

return ts, nil
Expand Down

0 comments on commit fa572d5

Please sign in to comment.