1// Copyright 2016 Google LLC 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 15package logadmin 16 17import ( 18 "context" 19 "errors" 20 "fmt" 21 22 vkit "cloud.google.com/go/logging/apiv2" 23 "google.golang.org/api/iterator" 24 logpb "google.golang.org/genproto/googleapis/logging/v2" 25 maskpb "google.golang.org/genproto/protobuf/field_mask" 26) 27 28// Sink describes a sink used to export log entries outside Stackdriver 29// Logging. Incoming log entries matching a filter are exported to a 30// destination (a Cloud Storage bucket, BigQuery dataset or Cloud Pub/Sub 31// topic). 32// 33// For more information, see https://cloud.google.com/logging/docs/export/using_exported_logs. 34// (The Sinks in this package are what the documentation refers to as "project sinks".) 35type Sink struct { 36 // ID is a client-assigned sink identifier. Example: 37 // "my-severe-errors-to-pubsub". 38 // Sink identifiers are limited to 1000 characters 39 // and can include only the following characters: A-Z, a-z, 40 // 0-9, and the special characters "_-.". 41 ID string 42 43 // Destination is the export destination. See 44 // https://cloud.google.com/logging/docs/api/tasks/exporting-logs. 45 // Examples: "storage.googleapis.com/a-bucket", 46 // "bigquery.googleapis.com/projects/a-project-id/datasets/a-dataset". 47 Destination string 48 49 // Filter optionally specifies an advanced logs filter (see 50 // https://cloud.google.com/logging/docs/view/advanced_filters) that 51 // defines the log entries to be exported. Example: "logName:syslog AND 52 // severity>=ERROR". If omitted, all entries are returned. 53 Filter string 54 55 // WriterIdentity must be a service account name. When exporting logs, Logging 56 // adopts this identity for authorization. The export destination's owner must 57 // give this service account permission to write to the export destination. 58 WriterIdentity string 59 60 // IncludeChildren, when set to true, allows the sink to export log entries from 61 // the organization or folder, plus (recursively) from any contained folders, billing 62 // accounts, or projects. IncludeChildren is false by default. You can use the sink's 63 // filter to choose log entries from specific projects, specific resource types, or 64 // specific named logs. 65 // 66 // Caution: If you enable this feature, your aggregated export sink might export 67 // a very large number of log entries. To avoid exporting too many log entries, 68 // design your aggregated export sink filter carefully, as described on 69 // https://cloud.google.com/logging/docs/export/aggregated_exports. 70 IncludeChildren bool 71} 72 73// CreateSink creates a Sink. It returns an error if the Sink already exists. 74// Requires AdminScope. 75func (c *Client) CreateSink(ctx context.Context, sink *Sink) (*Sink, error) { 76 return c.CreateSinkOpt(ctx, sink, SinkOptions{}) 77} 78 79// CreateSinkOpt creates a Sink using the provided options. It returns an 80// error if the Sink already exists. Requires AdminScope. 81func (c *Client) CreateSinkOpt(ctx context.Context, sink *Sink, opts SinkOptions) (*Sink, error) { 82 ls, err := c.sClient.CreateSink(ctx, &logpb.CreateSinkRequest{ 83 Parent: c.parent, 84 Sink: toLogSink(sink), 85 UniqueWriterIdentity: opts.UniqueWriterIdentity, 86 }) 87 if err != nil { 88 return nil, err 89 } 90 return fromLogSink(ls), nil 91} 92 93// SinkOptions define options to be used when creating or updating a sink. 94type SinkOptions struct { 95 // Determines the kind of IAM identity returned as WriterIdentity in the new 96 // sink. If this value is omitted or set to false, and if the sink's parent is a 97 // project, then the value returned as WriterIdentity is the same group or 98 // service account used by Stackdriver Logging before the addition of writer 99 // identities to the API. The sink's destination must be in the same project as 100 // the sink itself. 101 // 102 // If this field is set to true, or if the sink is owned by a non-project 103 // resource such as an organization, then the value of WriterIdentity will 104 // be a unique service account used only for exports from the new sink. 105 UniqueWriterIdentity bool 106 107 // These fields apply only to UpdateSinkOpt calls. The corresponding sink field 108 // is updated if and only if the Update field is true. 109 UpdateDestination bool 110 UpdateFilter bool 111 UpdateIncludeChildren bool 112} 113 114// DeleteSink deletes a sink. The provided sinkID is the sink's identifier, such as 115// "my-severe-errors-to-pubsub". 116// Requires AdminScope. 117func (c *Client) DeleteSink(ctx context.Context, sinkID string) error { 118 return c.sClient.DeleteSink(ctx, &logpb.DeleteSinkRequest{ 119 SinkName: c.sinkPath(sinkID), 120 }) 121} 122 123// Sink gets a sink. The provided sinkID is the sink's identifier, such as 124// "my-severe-errors-to-pubsub". 125// Requires ReadScope or AdminScope. 126func (c *Client) Sink(ctx context.Context, sinkID string) (*Sink, error) { 127 ls, err := c.sClient.GetSink(ctx, &logpb.GetSinkRequest{ 128 SinkName: c.sinkPath(sinkID), 129 }) 130 if err != nil { 131 return nil, err 132 } 133 return fromLogSink(ls), nil 134} 135 136// UpdateSink updates an existing Sink. Requires AdminScope. 137// 138// WARNING: UpdateSink will always update the Destination, Filter and IncludeChildren 139// fields of the sink, even if they have their zero values. Use UpdateSinkOpt 140// for more control over which fields to update. 141func (c *Client) UpdateSink(ctx context.Context, sink *Sink) (*Sink, error) { 142 return c.UpdateSinkOpt(ctx, sink, SinkOptions{ 143 UpdateDestination: true, 144 UpdateFilter: true, 145 UpdateIncludeChildren: true, 146 }) 147} 148 149// UpdateSinkOpt updates an existing Sink, using the provided options. Requires AdminScope. 150// 151// To change a sink's writer identity to a service account unique to the sink, set 152// opts.UniqueWriterIdentity to true. It is not possible to change a sink's writer identity 153// from a unique service account to a non-unique writer identity. 154func (c *Client) UpdateSinkOpt(ctx context.Context, sink *Sink, opts SinkOptions) (*Sink, error) { 155 mask := &maskpb.FieldMask{} 156 if opts.UpdateDestination { 157 mask.Paths = append(mask.Paths, "destination") 158 } 159 if opts.UpdateFilter { 160 mask.Paths = append(mask.Paths, "filter") 161 } 162 if opts.UpdateIncludeChildren { 163 mask.Paths = append(mask.Paths, "include_children") 164 } 165 if opts.UniqueWriterIdentity && len(mask.Paths) == 0 { 166 // Hack: specify a deprecated, unchangeable field so that we have a non-empty 167 // field mask. (An empty field mask would cause the destination, filter and include_children 168 // fields to be changed.) 169 mask.Paths = append(mask.Paths, "output_version_format") 170 } 171 if len(mask.Paths) == 0 { 172 return nil, errors.New("logadmin: UpdateSinkOpt: nothing to update") 173 } 174 ls, err := c.sClient.UpdateSink(ctx, &logpb.UpdateSinkRequest{ 175 SinkName: c.sinkPath(sink.ID), 176 Sink: toLogSink(sink), 177 UniqueWriterIdentity: opts.UniqueWriterIdentity, 178 UpdateMask: mask, 179 }) 180 if err != nil { 181 return nil, err 182 } 183 return fromLogSink(ls), err 184} 185 186func (c *Client) sinkPath(sinkID string) string { 187 return fmt.Sprintf("%s/sinks/%s", c.parent, sinkID) 188} 189 190// Sinks returns a SinkIterator for iterating over all Sinks in the Client's project. 191// Requires ReadScope or AdminScope. 192func (c *Client) Sinks(ctx context.Context) *SinkIterator { 193 it := &SinkIterator{ 194 it: c.sClient.ListSinks(ctx, &logpb.ListSinksRequest{Parent: c.parent}), 195 } 196 it.pageInfo, it.nextFunc = iterator.NewPageInfo( 197 it.fetch, 198 func() int { return len(it.items) }, 199 func() interface{} { b := it.items; it.items = nil; return b }) 200 return it 201} 202 203// A SinkIterator iterates over Sinks. 204type SinkIterator struct { 205 it *vkit.LogSinkIterator 206 pageInfo *iterator.PageInfo 207 nextFunc func() error 208 items []*Sink 209} 210 211// PageInfo supports pagination. See the google.golang.org/api/iterator package for details. 212func (it *SinkIterator) PageInfo() *iterator.PageInfo { return it.pageInfo } 213 214// Next returns the next result. Its second return value is Done if there are 215// no more results. Once Next returns Done, all subsequent calls will return 216// Done. 217func (it *SinkIterator) Next() (*Sink, error) { 218 if err := it.nextFunc(); err != nil { 219 return nil, err 220 } 221 item := it.items[0] 222 it.items = it.items[1:] 223 return item, nil 224} 225 226func (it *SinkIterator) fetch(pageSize int, pageToken string) (string, error) { 227 return iterFetch(pageSize, pageToken, it.it.PageInfo(), func() error { 228 item, err := it.it.Next() 229 if err != nil { 230 return err 231 } 232 it.items = append(it.items, fromLogSink(item)) 233 return nil 234 }) 235} 236 237func toLogSink(s *Sink) *logpb.LogSink { 238 return &logpb.LogSink{ 239 Name: s.ID, 240 Destination: s.Destination, 241 Filter: s.Filter, 242 IncludeChildren: s.IncludeChildren, 243 OutputVersionFormat: logpb.LogSink_V2, 244 // omit WriterIdentity because it is output-only. 245 } 246} 247 248func fromLogSink(ls *logpb.LogSink) *Sink { 249 return &Sink{ 250 ID: ls.Name, 251 Destination: ls.Destination, 252 Filter: ls.Filter, 253 WriterIdentity: ls.WriterIdentity, 254 IncludeChildren: ls.IncludeChildren, 255 } 256} 257