Skip to content

Commit

Permalink
feat(subscriber): introduce wildcard pattern (#95)
Browse files Browse the repository at this point in the history
Previously we were treating the absence of patterns or prefixes to
signify "subscribe all". Semantically this is quite confusing once you
try to wire it into cloudformation, where we would like to have the
presence of inputs gate the installation of our app.

This commit adds the ability to support wildcard on either
`logGroupNamePatterns` or `logGroupNamePrefixes`. The subscriber
cloudformation template is adjusted to still subscribe all log groups on
install by default.
  • Loading branch information
jta authored Nov 20, 2023
1 parent 6478444 commit 9c6ce40
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 24 deletions.
14 changes: 8 additions & 6 deletions apps/subscriber/template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,17 @@ Parameters:
LogGroupNamePatterns:
Type: CommaDelimitedList
Description: >-
Comma separated list of patterns. If not empty, the lambda function will
only apply to log groups that have names that match one of the provided
strings based on a case-sensitive substring search.
Default: ''
Comma separated list of patterns.
We will only subscribe to log groups that have names matching one of the
provided strings based on strings based on a case-sensitive substring
search. To subscribe to all log groups, use the wildcard operator *.
Default: '*'
LogGroupNamePrefixes:
Type: CommaDelimitedList
Description: >-
Comma separated list of prefixes. If not empty, the lambda function will
only apply to log groups that start with a provided string.
Comma separated list of prefixes. The lambda function will only apply to
log groups that start with a provided string. To subscribe to all log
groups, use the wildcard operator *.
Default: ''
NumWorkers:
Type: String
Expand Down
17 changes: 16 additions & 1 deletion docs/subscriber.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,22 @@ aws logs describe-log-groups --log-group-name-pattern prod
aws logs describe-log-groups --log-group-name-prefix /aws/lambda
```

If no patterns or prefixes are provided, the lambda function will list all log groups.
To subscribe to all log groups, a wildcard can be provided to either `logGroupNamePatterns` or `logGroupNamePrefixes`. The following input:

```json
{
"discover": {
"logGroupNamePatterns": [ "*" ]
}
}
```

Will trigger a paginated request equivalent to the `awscli` command:

```shell
aws logs describe-log-groups
```


### Response format

Expand Down
28 changes: 19 additions & 9 deletions handler/subscriber/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func (c *Config) Validate() error {
}

for _, s := range append(c.LogGroupNamePatterns, c.LogGroupNamePrefixes...) {
if !logGroupNameRe.MatchString(s) {
if !logGroupNameRe.MatchString(s) && s != "*" {
errs = append(errs, fmt.Errorf("%w: %q", ErrInvalidLogGroupName, s))
}
}
Expand All @@ -89,23 +89,33 @@ func (c *Config) Validate() error {
}

func (c *Config) LogGroupFilter() FilterFunc {
var re *regexp.Regexp
filterFunc := func(logGroupName string) bool {
if re != nil {
return re.MatchString(logGroupName)
}
return true
}

var exprs []string

exprs = append(exprs, c.LogGroupNamePatterns...)
for _, pattern := range c.LogGroupNamePatterns {
if pattern == "*" {
return filterFunc
}
exprs = append(exprs, pattern)
}

for _, prefix := range c.LogGroupNamePrefixes {
if prefix == "*" {
return filterFunc
}
exprs = append(exprs, fmt.Sprintf("^%s.*", prefix))
}

var re *regexp.Regexp
if len(exprs) != 0 {
re = regexp.MustCompile(strings.Join(exprs, "|"))
}

return func(logGroupName string) bool {
if re != nil {
return re.MatchString(logGroupName)
}
return true
}
return filterFunc
}
13 changes: 13 additions & 0 deletions handler/subscriber/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ func TestConfig(t *testing.T) {
},
ExpectError: subscriber.ErrInvalidLogGroupName,
},

{
Config: subscriber.Config{
CloudWatchLogsClient: &handlertest.CloudWatchLogsClient{},
Expand All @@ -69,6 +70,7 @@ func TestConfig(t *testing.T) {
Config: subscriber.Config{
FilterName: "ok",
CloudWatchLogsClient: &handlertest.CloudWatchLogsClient{},
LogGroupNamePrefixes: []string{"*"},
},
},
}
Expand Down Expand Up @@ -115,6 +117,17 @@ func TestLogFilter(t *testing.T) {
"dev-local": false,
},
},
{
Config: subscriber.Config{
LogGroupNamePatterns: []string{"prod", "*"},
DestinationARN: "hello",
},
Matches: map[string]bool{
"prod-1": true,
"eu-prod": true,
"staging": true,
},
},
}

for i, tc := range testcases {
Expand Down
21 changes: 20 additions & 1 deletion handler/subscriber/discovery_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ func TestHandleDiscovery(t *testing.T) {
ExpectJSONResponse string
}{
{
DiscoveryRequest: &subscriber.DiscoveryRequest{},
DiscoveryRequest: &subscriber.DiscoveryRequest{
LogGroupNamePatterns: []*string{aws.String("*")},
},
/* matches:
- /aws/hello
- /aws/ello
Expand All @@ -53,6 +55,23 @@ func TestHandleDiscovery(t *testing.T) {
}
}`,
},
{
DiscoveryRequest: &subscriber.DiscoveryRequest{},
/* matches nothing
*/
ExpectJSONResponse: `{
"discovery": {
"logGroupCount": 0,
"requestCount": 0,
"subscription": {
"deleted": 0,
"updated": 0,
"skipped": 0,
"processed": 0
}
}
}`,
},
{
DiscoveryRequest: &subscriber.DiscoveryRequest{
LogGroupNamePrefixes: []*string{
Expand Down
22 changes: 15 additions & 7 deletions handler/subscriber/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"errors"
"fmt"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs"
)

Expand Down Expand Up @@ -90,25 +91,32 @@ func (d *DiscoveryRequest) ToDescribeLogInputs() (inputs []*cloudwatchlogs.Descr
}

for _, pattern := range d.LogGroupNamePatterns {
if aws.ToString(pattern) == "*" {
return []*cloudwatchlogs.DescribeLogGroupsInput{
{
Limit: d.Limit,
},
}
}
inputs = append(inputs, &cloudwatchlogs.DescribeLogGroupsInput{
LogGroupNamePattern: pattern,
Limit: d.Limit,
})
}

for _, prefix := range d.LogGroupNamePrefixes {
if aws.ToString(prefix) == "*" {
return []*cloudwatchlogs.DescribeLogGroupsInput{
{
Limit: d.Limit,
},
}
}
inputs = append(inputs, &cloudwatchlogs.DescribeLogGroupsInput{
LogGroupNamePrefix: prefix,
Limit: d.Limit,
})
}

if len(inputs) == 0 {
// We should list all since we were provided with no log groups
// or filters.
inputs = append(inputs, &cloudwatchlogs.DescribeLogGroupsInput{
Limit: d.Limit,
})
}
return inputs
}
1 change: 1 addition & 0 deletions integration/scripts/check_subscriber
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ AWS_REGION=$(echo "$FUNCTION_ARN" | cut -d: -f4)
check_result() {
ERR=$(jq '.StatusCode != 200 or has("FunctionError")' <<<"$1")
if [[ "$ERR" == true ]]; then
cat ${TMPFILE}
echo "$1"
return 1
fi
Expand Down

0 comments on commit 9c6ce40

Please sign in to comment.