pulumi/pkg/operations/operations_gcp.go

157 lines
4.7 KiB
Go
Raw Normal View History

// Copyright 2016-2019, Pulumi Corporation.
//
// 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 operations
import (
"context"
"encoding/json"
"fmt"
"reflect"
"strings"
"time"
gcplogging "cloud.google.com/go/logging/apiv2"
"google.golang.org/api/iterator"
"google.golang.org/api/option"
loggingpb "google.golang.org/genproto/googleapis/logging/v2"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/config"
"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
)
// TODO[pulumi/pulumi#54] This should be factored out behind an OperationsProvider RPC interface and versioned with the
// `pulumi-gcp` repo instead of statically linked into the engine.
// GCPOperationsProvider creates an OperationsProvider capable of answering operational queries based on the
// underlying resources of the `@pulumi/gcp` implementation.
func GCPOperationsProvider(
config map[config.Key]string,
all: Reformat with gofumpt Per team discussion, switching to gofumpt. [gofumpt][1] is an alternative, stricter alternative to gofmt. It addresses other stylistic concerns that gofmt doesn't yet cover. [1]: https://github.com/mvdan/gofumpt See the full list of [Added rules][2], but it includes: - Dropping empty lines around function bodies - Dropping unnecessary variable grouping when there's only one variable - Ensuring an empty line between multi-line functions - simplification (`-s` in gofmt) is always enabled - Ensuring multi-line function signatures end with `) {` on a separate line. [2]: https://github.com/mvdan/gofumpt#Added-rules gofumpt is stricter, but there's no lock-in. All gofumpt output is valid gofmt output, so if we decide we don't like it, it's easy to switch back without any code changes. gofumpt support is built into the tooling we use for development so this won't change development workflows. - golangci-lint includes a gofumpt check (enabled in this PR) - gopls, the LSP for Go, includes a gofumpt option (see [installation instrutions][3]) [3]: https://github.com/mvdan/gofumpt#installation This change was generated by running: ```bash gofumpt -w $(rg --files -g '*.go' | rg -v testdata | rg -v compilation_error) ``` The following files were manually tweaked afterwards: - pkg/cmd/pulumi/stack_change_secrets_provider.go: one of the lines overflowed and had comments in an inconvenient place - pkg/cmd/pulumi/destroy.go: `var x T = y` where `T` wasn't necessary - pkg/cmd/pulumi/policy_new.go: long line because of error message - pkg/backend/snapshot_test.go: long line trying to assign three variables in the same assignment I have included mention of gofumpt in the CONTRIBUTING.md.
2023-03-03 16:36:39 +00:00
component *Resource,
) (Provider, error) {
ctx := context.TODO()
client, err := gcplogging.NewClient(ctx, option.WithScopes("https://www.googleapis.com/auth/logging.read"))
if err != nil {
return nil, err
}
prov := &gcpOpsProvider{
ctx: ctx,
client: client,
component: component,
}
return prov, nil
}
type gcpOpsProvider struct {
ctx context.Context
client *gcplogging.Client
component *Resource
}
var _ Provider = (*gcpOpsProvider)(nil)
const (
// GCP resource types
gcpFunctionType = tokens.Type("gcp:cloudfunctions/function:Function")
)
func (ops *gcpOpsProvider) GetLogs(query LogQuery) (*[]LogEntry, error) {
state := ops.component.State
logging.V(6).Infof("GetLogs[%v]", state.URN)
//exhaustive:ignore
switch state.Type {
case gcpFunctionType:
return ops.getFunctionLogs(state, query)
default:
// Else this resource kind does not produce any logs.
logging.V(6).Infof("GetLogs[%v] does not produce logs", state.URN)
return nil, nil
}
}
func (ops *gcpOpsProvider) getFunctionLogs(state *resource.State, query LogQuery) (*[]LogEntry, error) {
name := state.Outputs["name"].StringValue()
project := state.Outputs["project"].StringValue()
region := state.Outputs["region"].StringValue()
// These filters mirror what `gcloud functions logs read [function-name]` does to filter.
logFilter := []string{
`resource.type="cloud_function"`,
`resource.labels.region="` + region + `"`,
`logName:"cloud-functions"`,
`resource.labels.function_name="` + name + `"`,
}
if query.StartTime != nil {
logFilter = append(logFilter, fmt.Sprintf(`timestamp>="%s"`, query.StartTime.Format(time.RFC3339)))
}
if query.EndTime != nil {
logFilter = append(logFilter, fmt.Sprintf(`timestamp<="%s"`, query.EndTime.Format(time.RFC3339)))
}
req := &loggingpb.ListLogEntriesRequest{
ResourceNames: []string{"projects/" + project},
Filter: strings.Join(logFilter, " "),
}
var logs []LogEntry
it := ops.client.ListLogEntries(ops.ctx, req)
for {
entry, err := it.Next()
if err == iterator.Done {
logging.V(5).Infof("GetLogs[%v] return %d logs", state.URN, len(logs))
return &logs, nil
}
if err != nil {
logging.V(5).Infof("GetLogs[%v] error iterating logs: %s", state.URN, err)
return nil, err
}
message, err := getLogEntryMessage(entry)
if err != nil {
logging.V(5).Infof("GetLogs[%v] error getting entry message, ignoring: %s", state.URN, err)
continue
}
logs = append(logs, LogEntry{
ID: name,
Message: message,
Timestamp: entry.GetTimestamp().Seconds * 1000,
})
}
}
// getLogEntryMessage gets the message for a log entry. There are many different underlying types for the message
// payload. If we don't know how to decode a payload to a string, an error is returned.
func getLogEntryMessage(e *loggingpb.LogEntry) (string, error) {
switch payload := e.GetPayload().(type) {
case *loggingpb.LogEntry_TextPayload:
return payload.TextPayload, nil
case *loggingpb.LogEntry_JsonPayload:
byts, err := json.Marshal(payload.JsonPayload)
if err != nil {
return "", fmt.Errorf("encoding to JSON: %w", err)
}
return string(byts), nil
default:
return "", fmt.Errorf("can't decode entry of type %s", reflect.TypeOf(e))
}
}