-
Notifications
You must be signed in to change notification settings - Fork 61
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: minimize dependencies on 3P libraries (#101)
This keeps the functions framework as lightweight as possible, and avoids pulling in all of the Google Cloud client libraries even though only one package is needed. The new `internal/metadata` package is just a copy of [google-cloud-go/functions/metadata](https://github.com/googleapis/google-cloud-go/tree/main/functions/metadata) Minimizing what transitive dependencies the Functions Framework has is important to make them work with buildpacks, where user function code has to be bundled together with the functions framework. If a function's dependency conflicts with a Functions Framework dependency and the function is using vendor, then there can be undefined behavior at build and runtime.
- Loading branch information
Showing
10 changed files
with
362 additions
and
413 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
// Copyright 2021 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 metadata | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"time" | ||
) | ||
|
||
// Metadata holds Google Cloud Functions metadata. | ||
type Metadata struct { | ||
// EventID is a unique ID for the event. For example: "70172329041928". | ||
EventID string `json:"eventId"` | ||
// Timestamp is the date/time this event was created. | ||
Timestamp time.Time `json:"timestamp"` | ||
// EventType is the type of the event. For example: "google.pubsub.topic.publish". | ||
EventType string `json:"eventType"` | ||
// Resource is the resource that triggered the event. | ||
Resource *Resource `json:"resource"` | ||
} | ||
|
||
// Resource holds Google Cloud Functions resource metadata. | ||
// Resource values are dependent on the event type they're from. | ||
type Resource struct { | ||
// Service is the service that triggered the event. | ||
Service string `json:"service"` | ||
// Name is the name associated with the event. | ||
Name string `json:"name"` | ||
// Type is the type of event. | ||
Type string `json:"type"` | ||
// Path is the path to the resource type (deprecated). | ||
// This is the case for some deprecated GCS | ||
// notifications, which populate the resource field as a string containing the topic | ||
// rather than as the expected dictionary. | ||
// See the Attributes section of https://cloud.google.com/storage/docs/pubsub-notifications | ||
// for more details. | ||
RawPath string `json:"-"` | ||
} | ||
|
||
// UnmarshalJSON specializes the Resource unmarshalling to handle the case where the | ||
// value is a string instead of a map. See the comment above on RawPath for why this | ||
// needs to be handled. | ||
func (r *Resource) UnmarshalJSON(data []byte) error { | ||
// Try to unmarshal the resource into a string. | ||
var path string | ||
if err := json.Unmarshal(data, &path); err == nil { | ||
r.RawPath = path | ||
return nil | ||
} | ||
|
||
// Otherwise, accept whatever the result of the normal unmarshal would be. | ||
// Need to define a new type, otherwise it infinitely recurses and panics. | ||
type resource Resource | ||
var res resource | ||
if err := json.Unmarshal(data, &res); err != nil { | ||
return err | ||
} | ||
|
||
r.Service = res.Service | ||
r.Name = res.Name | ||
r.Type = res.Type | ||
return nil | ||
} | ||
|
||
// MarshalJSON specializes the Resource marshalling to handle the case where the | ||
// value is a string instead of a map. See the comment above on RawPath for why this | ||
// needs to be handled. | ||
func (r *Resource) MarshalJSON() ([]byte, error) { | ||
// If RawPath is set, use that as the whole value. | ||
if r.RawPath != "" { | ||
return []byte(fmt.Sprintf("%q", r.RawPath)), nil | ||
} | ||
|
||
// Otherwise, accept whatever the result of the normal marshal would be. | ||
res := *r | ||
b, err := json.Marshal(res) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return b, nil | ||
} | ||
|
||
type contextKey string | ||
|
||
// GCFContextKey satisfies an interface to be able to use contextKey to read | ||
// metadata from a Cloud Functions context.Context. | ||
// | ||
// Be careful making changes to this function. See FromContext. | ||
func (k contextKey) GCFContextKey() string { | ||
return string(k) | ||
} | ||
|
||
const metadataContextKey = contextKey("metadata") | ||
|
||
// FromContext extracts the Metadata from the Context, if present. | ||
func FromContext(ctx context.Context) (*Metadata, error) { | ||
if ctx == nil { | ||
return nil, errors.New("nil ctx") | ||
} | ||
// The original JSON is inserted by the Cloud Functions worker. So, the | ||
// format must not change, or the message may fail to unmarshal. We use | ||
// JSON as a common format between the worker and this package to ensure | ||
// this package can be updated independently from the worker. The contextKey | ||
// type and the metadataContextKey value use an interface to avoid using | ||
// a built-in type as a context key (which is easy to have collisions with). | ||
// If we need another value to be stored in the context, we can use a new | ||
// key or interface and avoid needing to change this one. Similarly, if we | ||
// need to change the format of the message, we should add an additional key | ||
// to keep backward compatibility. | ||
b, ok := ctx.Value(metadataContextKey).(json.RawMessage) | ||
if !ok { | ||
return nil, errors.New("unable to find metadata") | ||
} | ||
meta := &Metadata{} | ||
if err := json.Unmarshal(b, meta); err != nil { | ||
return nil, fmt.Errorf("json.Unmarshal: %v", err) | ||
} | ||
return meta, nil | ||
} | ||
|
||
// NewContext returns a new Context carrying m. If m is nil, NewContext returns | ||
// ctx. NewContext is only used for writing tests which rely on Metadata. | ||
func NewContext(ctx context.Context, m *Metadata) context.Context { | ||
if m == nil { | ||
return ctx | ||
} | ||
b, err := json.Marshal(m) | ||
if err != nil { | ||
return ctx | ||
} | ||
return context.WithValue(ctx, metadataContextKey, json.RawMessage(b)) | ||
} |
Oops, something went wrong.