github.com/GoogleCloudPlatform/testgrid@v0.0.174/pkg/pubsub/pubsub.go (about) 1 /* 2 Copyright 2021 The TestGrid Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package pubsub exports messages for interacting with pubsub. 18 package pubsub 19 20 import ( 21 "context" 22 "fmt" 23 "strconv" 24 "time" 25 26 "cloud.google.com/go/pubsub" 27 "github.com/GoogleCloudPlatform/testgrid/util/gcs" 28 "github.com/sirupsen/logrus" 29 ) 30 31 // Subscriber creates Senders that attach to subscriptions with the specified settings. 32 type Subscriber interface { 33 Subscribe(projID, subID string, setttings *pubsub.ReceiveSettings) Sender 34 } 35 36 // Client wraps a pubsub client into a Subscriber that creates Senders for pubsub subscriptions. 37 type Client pubsub.Client 38 39 // NewClient converts a raw pubsub client into a Subscriber. 40 func NewClient(client *pubsub.Client) *Client { 41 return (*Client)(client) 42 } 43 44 // Subscribe to the specified id in the project using optional receive settings. 45 func (c *Client) Subscribe(projID, subID string, settings *pubsub.ReceiveSettings) Sender { 46 sub := (*pubsub.Client)(c).SubscriptionInProject(subID, projID) 47 if settings != nil { 48 sub.ReceiveSettings = *settings 49 } 50 return sub.Receive 51 } 52 53 // Sender forwards pubsub messages to the receive function until the send context expires. 54 type Sender func(sendCtx context.Context, receive func(context.Context, *pubsub.Message)) error 55 56 const ( 57 keyBucket = "bucketId" 58 keyObject = "objectId" 59 keyEvent = "eventType" 60 keyTime = "eventTime" 61 keyGeneration = "objectGeneration" 62 ) 63 64 // Event specifies what happened to the GCS object. 65 // 66 // See https://cloud.google.com/storage/docs/pubsub-notifications#events 67 type Event string 68 69 // Well-known event types. 70 const ( 71 Finalize Event = "OBJECT_FINALIZE" 72 Delete Event = "OBJECT_DELETE" 73 ) 74 75 // Notification captures information about a change to a GCS object. 76 type Notification struct { 77 Path gcs.Path 78 Event Event 79 Time time.Time 80 Generation int64 81 } 82 83 func (n Notification) String() string { 84 return fmt.Sprintf("%s#%d %s at %s", n.Path, n.Generation, n.Event, n.Time) 85 } 86 87 // SendGCS converts GCS pubsub messages into Notification structs and sends them to receivers. 88 // 89 // Connects to the specified subscription with optionally specified settings. 90 // Receives pubsub messages from this subscription and converts it into a Notification struct. 91 // - Nacks any message it cannot parse. 92 // Sends the notification to the receivers channel. 93 // - Nacks messages associated with any unsent Notifications. 94 // - Acks as soon as the Notification is sent. 95 // 96 // More info: https://cloud.google.com/storage/docs/pubsub-notifications#overview 97 func SendGCS(ctx context.Context, log logrus.FieldLogger, client Subscriber, projectID, subID string, settings *pubsub.ReceiveSettings, receivers chan<- *Notification) error { 98 l := log.WithField("subscription", "pubsub://"+projectID+"/"+subID) 99 send := client.Subscribe(projectID, subID, settings) 100 l.Trace("Subscribing...") 101 return sendToReceivers(ctx, l, send, receivers, realAcker{}) 102 } 103 104 type acker interface { 105 Ack(*pubsub.Message) 106 Nack(*pubsub.Message) 107 } 108 109 type realAcker struct{} 110 111 func (ra realAcker) Ack(m *pubsub.Message) { 112 m.Ack() 113 } 114 115 func (ra realAcker) Nack(m *pubsub.Message) { 116 m.Nack() 117 } 118 119 func sendToReceivers(ctx context.Context, log logrus.FieldLogger, send Sender, receivers chan<- *Notification, result acker) error { 120 return send(ctx, func(ctx context.Context, msg *pubsub.Message) { 121 bucket, obj := msg.Attributes[keyBucket], msg.Attributes[keyObject] 122 path, err := gcs.NewPath("gs://" + bucket + "/" + obj) 123 if err != nil { 124 log.WithError(err).WithFields(logrus.Fields{ 125 "bucket": bucket, 126 "object": obj, 127 "id": msg.ID, 128 }).Error("Failed to parse path") 129 result.Ack(msg) 130 return 131 } 132 when, err := time.Parse(time.RFC3339, msg.Attributes[keyTime]) 133 if err != nil { 134 log.WithError(err).WithFields(logrus.Fields{ 135 "time": msg.Attributes[keyTime], 136 "id": msg.ID, 137 }).Error("Failed to parse time") 138 result.Nack(msg) 139 return 140 } 141 gen, err := strconv.ParseInt(msg.Attributes[keyGeneration], 10, 64) 142 if err != nil { 143 log.WithError(err).WithFields(logrus.Fields{ 144 "generation": msg.Attributes[keyGeneration], 145 "id": msg.ID, 146 }).Error("Failed to parse generation") 147 result.Nack(msg) 148 return 149 } 150 notice := Notification{ 151 Path: *path, 152 Event: Event(msg.Attributes[keyEvent]), 153 Time: when, 154 Generation: gen, 155 } 156 select { 157 case <-ctx.Done(): 158 result.Nack(msg) 159 case receivers <- ¬ice: 160 result.Ack(msg) 161 } 162 }) 163 }