github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/operations/operations_gcp.go (about) 1 // Copyright 2016-2019, Pulumi Corporation. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package operations 16 17 import ( 18 "context" 19 "encoding/json" 20 "fmt" 21 "reflect" 22 "strings" 23 "time" 24 25 gcplogging "cloud.google.com/go/logging/apiv2" 26 "google.golang.org/api/iterator" 27 "google.golang.org/api/option" 28 loggingpb "google.golang.org/genproto/googleapis/logging/v2" 29 30 "github.com/pulumi/pulumi/sdk/v3/go/common/resource" 31 "github.com/pulumi/pulumi/sdk/v3/go/common/resource/config" 32 "github.com/pulumi/pulumi/sdk/v3/go/common/tokens" 33 "github.com/pulumi/pulumi/sdk/v3/go/common/util/logging" 34 ) 35 36 // TODO[pulumi/pulumi#54] This should be factored out behind an OperationsProvider RPC interface and versioned with the 37 // `pulumi-gcp` repo instead of statically linked into the engine. 38 39 // GCPOperationsProvider creates an OperationsProvider capable of answering operational queries based on the 40 // underlying resources of the `@pulumi/gcp` implementation. 41 func GCPOperationsProvider( 42 config map[config.Key]string, 43 component *Resource) (Provider, error) { 44 45 ctx := context.TODO() 46 client, err := gcplogging.NewClient(ctx, option.WithScopes("https://www.googleapis.com/auth/logging.read")) 47 if err != nil { 48 return nil, err 49 } 50 51 prov := &gcpOpsProvider{ 52 ctx: ctx, 53 client: client, 54 component: component, 55 } 56 return prov, nil 57 } 58 59 type gcpOpsProvider struct { 60 ctx context.Context 61 client *gcplogging.Client 62 component *Resource 63 } 64 65 var _ Provider = (*gcpOpsProvider)(nil) 66 67 const ( 68 // GCP resource types 69 gcpFunctionType = tokens.Type("gcp:cloudfunctions/function:Function") 70 ) 71 72 func (ops *gcpOpsProvider) GetLogs(query LogQuery) (*[]LogEntry, error) { 73 state := ops.component.State 74 logging.V(6).Infof("GetLogs[%v]", state.URN) 75 switch state.Type { 76 case gcpFunctionType: 77 return ops.getFunctionLogs(state, query) 78 default: 79 // Else this resource kind does not produce any logs. 80 logging.V(6).Infof("GetLogs[%v] does not produce logs", state.URN) 81 return nil, nil 82 } 83 } 84 85 func (ops *gcpOpsProvider) getFunctionLogs(state *resource.State, query LogQuery) (*[]LogEntry, error) { 86 name := state.Outputs["name"].StringValue() 87 project := state.Outputs["project"].StringValue() 88 region := state.Outputs["region"].StringValue() 89 90 // These filters mirror what `gcloud functions logs read [function-name]` does to filter. 91 logFilter := []string{ 92 `resource.type="cloud_function"`, 93 `resource.labels.region="` + region + `"`, 94 `logName:"cloud-functions"`, 95 `resource.labels.function_name="` + name + `"`, 96 } 97 98 if query.StartTime != nil { 99 logFilter = append(logFilter, fmt.Sprintf(`timestamp>="%s"`, query.StartTime.Format(time.RFC3339))) 100 } 101 102 if query.EndTime != nil { 103 logFilter = append(logFilter, fmt.Sprintf(`timestamp<="%s"`, query.EndTime.Format(time.RFC3339))) 104 } 105 106 req := &loggingpb.ListLogEntriesRequest{ 107 ResourceNames: []string{"projects/" + project}, 108 Filter: strings.Join(logFilter, " "), 109 } 110 111 var logs []LogEntry 112 113 it := ops.client.ListLogEntries(ops.ctx, req) 114 for { 115 entry, err := it.Next() 116 if err == iterator.Done { 117 logging.V(5).Infof("GetLogs[%v] return %d logs", state.URN, len(logs)) 118 return &logs, nil 119 } 120 if err != nil { 121 logging.V(5).Infof("GetLogs[%v] error iterating logs: %s", state.URN, err) 122 return nil, err 123 } 124 125 message, err := getLogEntryMessage(entry) 126 if err != nil { 127 logging.V(5).Infof("GetLogs[%v] error getting entry message, ignoring: %s", state.URN, err) 128 continue 129 } 130 131 logs = append(logs, LogEntry{ 132 ID: name, 133 Message: message, 134 Timestamp: entry.GetTimestamp().Seconds * 1000, 135 }) 136 } 137 } 138 139 // getLogEntryMessage gets the message for a log entry. There are many different underlying types for the message 140 // payload. If we don't know how to decode a payload to a string, an error is returned. 141 func getLogEntryMessage(e *loggingpb.LogEntry) (string, error) { 142 switch payload := e.GetPayload().(type) { 143 case *loggingpb.LogEntry_TextPayload: 144 return payload.TextPayload, nil 145 146 case *loggingpb.LogEntry_JsonPayload: 147 byts, err := json.Marshal(payload.JsonPayload) 148 if err != nil { 149 return "", fmt.Errorf("encoding to JSON: %w", err) 150 } 151 return string(byts), nil 152 default: 153 return "", fmt.Errorf("can't decode entry of type %s", reflect.TypeOf(e)) 154 } 155 }