Skip to content

Commit

Permalink
listgroupmembers rc
Browse files Browse the repository at this point in the history
  • Loading branch information
BrunoReboul committed Mar 27, 2020
1 parent 0e93b64 commit 413e71d
Show file tree
Hide file tree
Showing 2 changed files with 244 additions and 0 deletions.
229 changes: 229 additions & 0 deletions listgroupmembers/listgroupmembers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
// Copyright 2020 Google LLC
//
// 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 listgroupmembers

import (
"context"
"encoding/json"
"fmt"
"log"
"os"
"sync"
"time"

"github.com/BrunoReboul/ram/helper"
"google.golang.org/api/option"

"cloud.google.com/go/pubsub"
admin "google.golang.org/api/admin/directory/v1"
)

// Global variable to deal with GroupsListCall Pages constraint: no possible to pass variable to the function in pages()
// https://pkg.go.dev/google.golang.org/api/admin/directory/v1?tab=doc#GroupsListCall.Pages
var ancestors []string
var ctx context.Context
var groupAssetName string
var groupEmail string
var logEventEveryXPubSubMsg uint64
var pubSubClient *pubsub.Client
var outputTopicName string
var pubSubErrNumber uint64
var pubSubMsgNumber uint64
var timestamp time.Time
var origin string

// Global structure for global variables to optimize the cloud function performances
type Global struct {
ctx context.Context
dirAdminService *admin.Service
initFailed bool
inputTopicName string
logEventEveryXPubSubMsg uint64
maxResultsPerPage int64 // API Max = 200
outputTopicName string
pubSubClient *pubsub.Client
retryTimeOutSeconds int64
}

// FeedMessageGroup CAI like format
type FeedMessageGroup struct {
Asset AssetGroup `json:"asset"`
Window helper.Window `json:"window"`
Deleted bool `json:"deleted"`
Origin string `json:"origin"`
}

// AssetGroup CAI like format
type AssetGroup struct {
Name string `json:"name"`
AssetType string `json:"assetType"`
Ancestors []string `json:"ancestors"`
IamPolicy json.RawMessage `json:"iamPolicy"`
Resource admin.Group `json:"resource"`
}

// FeedMessageMember CAI like format
type FeedMessageMember struct {
Asset AssetMember `json:"asset"`
Window helper.Window `json:"window"`
Deleted bool `json:"deleted"`
Origin string `json:"origin"`
}

// AssetMember CAI like format
type AssetMember struct {
Name string `json:"name"`
AssetType string `json:"assetType"`
Ancestors []string `json:"ancestors"`
AncestryPath string `json:"ancestryPath"`
IamPolicy json.RawMessage `json:"iamPolicy"`
Resource Member `json:"resource"`
}

// Member is sligthly different from admim.Member to have both group email and member email
type Member struct {
MemberEmail string `json:"memberEmail"`
GroupEmail string `json:"groupEmail"`
ID string `json:"id"`
Kind string `json:"kind"`
Role string `json:"role"`
Type string `json:"type"`
}

// Initialize is to be executed in the init() function of the cloud function to optimize the cold start
func Initialize(ctx context.Context, global *Global) {
global.ctx = ctx
global.initFailed = false

// err is pre-declared to avoid shadowing client.
var clientOption option.ClientOption
var err error
var gciAdminUserToImpersonate string
var keyJSONFilePath string
var ok bool
var projectID string
var serviceAccountEmail string

gciAdminUserToImpersonate = os.Getenv("GCIADMINUSERTOIMPERSONATE")
global.outputTopicName = os.Getenv("OUTPUTTOPICNAME")
keyJSONFilePath = "./" + os.Getenv("KEYJSONFILENAME")
projectID = os.Getenv("GCP_PROJECT")
serviceAccountEmail = os.Getenv("SERVICEACCOUNTNAME")

log.Println("Function COLD START")
if global.retryTimeOutSeconds, ok = helper.GetEnvVarInt64("RETRYTIMEOUTSECONDS"); !ok {
return
}
if global.logEventEveryXPubSubMsg, ok = helper.GetEnvVarUint64("LOGEVENTEVERYXPUBSUBMSG"); !ok {
return
}
if global.maxResultsPerPage, ok = helper.GetEnvVarInt64("MAXRESULTSPERPAGE"); !ok {
return
}
if clientOption, ok = helper.GetClientOptionAndCleanKeys(ctx, serviceAccountEmail, keyJSONFilePath, projectID, gciAdminUserToImpersonate); !ok {
return
}
global.dirAdminService, err = admin.NewService(ctx, clientOption)
if err != nil {
log.Printf("ERROR - admin.NewService: %v", err)
global.initFailed = true
return
}

global.pubSubClient, err = pubsub.NewClient(ctx, projectID)
if err != nil {
log.Printf("ERROR - pubsub.NewClient: %v", err)
global.initFailed = true
return
}
}

// EntryPoint is the function to be executed for each cloud function occurence
func EntryPoint(ctxEvent context.Context, PubSubMessage helper.PubSubMessage, global *Global) error {
// log.Println(string(PubSubMessage.Data))
ok, metadata, err := helper.IntialRetryCheck(ctxEvent, global.initFailed, global.retryTimeOutSeconds)
if !ok {
return err
}
// log.Printf("EventType %s EventID %s Resource %s Timestamp %v", metadata.EventType, metadata.EventID, metadata.Resource.Type, metadata.Timestamp)

// Pass data to global variables to deal with func browseGroup
ctx = global.ctx
logEventEveryXPubSubMsg = global.logEventEveryXPubSubMsg
pubSubClient = global.pubSubClient
outputTopicName = global.outputTopicName
timestamp = metadata.Timestamp

var feedMessageGroup FeedMessageGroup
err = json.Unmarshal(PubSubMessage.Data, &feedMessageGroup)
if err != nil {
log.Println("ERROR - json.Unmarshal(pubSubMessage.Data, &feedMessageGroup)")
return nil // NO RETRY
}

pubSubMsgNumber = 0
groupAssetName = feedMessageGroup.Asset.Name
groupEmail = feedMessageGroup.Asset.Resource.Email
// First ancestor is my parent
ancestors = []string{fmt.Sprintf("groups/%s", feedMessageGroup.Asset.Resource.Id)}
// Next ancestors are my parent ancestors
for _, ancestor := range feedMessageGroup.Asset.Ancestors {
ancestors = append(ancestors, ancestor)
}
origin = feedMessageGroup.Origin
// pages function except just the name of the callback function. Not an invocation of the function
err = global.dirAdminService.Members.List(feedMessageGroup.Asset.Resource.Id).MaxResults(global.maxResultsPerPage).Pages(ctx, browseMembers)
if err != nil {
return fmt.Errorf("dirAdminService.Members.List: %v", err) // RETRY
}
log.Printf("Completed - Group %s %s Number of members published to pubsub topic %s: %d", feedMessageGroup.Asset.Resource.Id, feedMessageGroup.Asset.Resource.Email, outputTopicName, pubSubMsgNumber)
return nil
}

// browseMembers is executed for each page returning a set of members
// A non-nil error returned will halt the iteration
// the only accepted parameter is groups: https://pkg.go.dev/google.golang.org/api/admin/directory/v1?tab=doc#GroupsListCall.Pages
// so, it use global variables to this package
func browseMembers(members *admin.Members) error {
var waitgroup sync.WaitGroup
topic := pubSubClient.Topic(outputTopicName)
for _, member := range members.Members {
var feedMessageMember FeedMessageMember
feedMessageMember.Window.StartTime = timestamp
feedMessageMember.Origin = origin
feedMessageMember.Asset.Ancestors = ancestors
feedMessageMember.Asset.AncestryPath = groupAssetName
feedMessageMember.Asset.AssetType = "www.googleapis.com/admin/directory/members"
feedMessageMember.Asset.Name = groupAssetName + "/members/" + member.Id
feedMessageMember.Asset.Resource.GroupEmail = groupEmail
feedMessageMember.Asset.Resource.MemberEmail = member.Email
feedMessageMember.Asset.Resource.ID = member.Id
feedMessageMember.Asset.Resource.Kind = member.Kind
feedMessageMember.Asset.Resource.Role = member.Role
feedMessageMember.Asset.Resource.Type = member.Type
feedMessageMemberJSON, err := json.Marshal(feedMessageMember)
if err != nil {
log.Printf("ERROR - %s json.Marshal(feedMessageMember): %v", member.Email, err)
} else {
pubSubMessage := &pubsub.Message{
Data: feedMessageMemberJSON,
}
publishResult := topic.Publish(ctx, pubSubMessage)
waitgroup.Add(1)
go helper.GetPublishCallResult(ctx, publishResult, &waitgroup, groupAssetName+"/"+member.Email, &pubSubErrNumber, &pubSubMsgNumber, logEventEveryXPubSubMsg)
}
}
return nil
}
15 changes: 15 additions & 0 deletions listgroupmembers/listgroupmembers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2020 Google LLC
//
// 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 listgroupmembers

0 comments on commit 413e71d

Please sign in to comment.