From 9d047cc4e1af036a3fe58cba57fca1fec765e25b Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 30 Nov 2024 18:18:50 +0330 Subject: [PATCH] feat: changed module name --- command/cmd/describer.go | 10 +- command/cmd/getDescriber.go | 10 +- command/main.go | 2 +- go.mod | 2 +- main.go | 2 +- pkg/describer/resource_sender.go | 2 +- pkg/describer/resources.go | 8 +- pkg/describer/worker.go | 8 +- pkg/sdk/models/resource_type.go | 2 +- pkg/sdk/nats.go | 4 +- pkg/sdk/runable/resource_type/main.go | 2 +- .../steampipe_es_client_generator/main.go | 2 +- pkg/sdk/runable/steampipe_index_map/main.go | 2 +- provider/credentials.go | 4 +- provider/describer_wrapper.go | 4 +- provider/resource_types.go | 2 +- service/service.go | 2 +- .../.github/ISSUE_TEMPLATE/bug_report.md | 26 + .../.github/ISSUE_TEMPLATE/config.yml | 8 + .../feature-request---new-table.md | 11 + .../.github/ISSUE_TEMPLATE/feature_request.md | 20 + .../.github/PULL_REQUEST_TEMPLATE.md | 8 + .../.github/dependabot.yml | 18 + .../.github/workflows/golangci-lint.yml | 12 + .../.github/workflows/registry-publish.yml | 13 + .../.github/workflows/stale.yml | 17 + .../.github/workflows/steampipe-anywhere.yml | 12 + .../.github/workflows/sync-labels.yml | 9 + steampipe-plugin-googleworkspace/.gitignore | 15 + .../.goreleaser.yml | 37 + steampipe-plugin-googleworkspace/CHANGELOG.md | 138 ++ steampipe-plugin-googleworkspace/LICENSE | 201 +++ steampipe-plugin-googleworkspace/Makefile | 4 + steampipe-plugin-googleworkspace/README.md | 101 ++ .../config/googleworkspace.spc | 24 + steampipe-plugin-googleworkspace/docs/LICENSE | 402 ++++++ .../docs/index.md | 120 ++ .../docs/tables/googleworkspace_calendar.md | 42 + .../tables/googleworkspace_calendar_event.md | 211 +++ .../googleworkspace_calendar_my_event.md | 200 +++ .../docs/tables/googleworkspace_drive.md | 152 +++ .../tables/googleworkspace_drive_my_file.md | 130 ++ .../tables/googleworkspace_gmail_draft.md | 147 +++ .../tables/googleworkspace_gmail_message.md | 182 +++ .../tables/googleworkspace_gmail_my_draft.md | 134 ++ .../googleworkspace_gmail_my_message.md | 167 +++ .../googleworkspace_gmail_my_settings.md | 138 ++ .../tables/googleworkspace_gmail_settings.md | 151 +++ .../tables/googleworkspace_people_contact.md | 91 ++ .../googleworkspace_people_contact_group.md | 116 ++ ...googleworkspace_people_directory_people.md | 49 + steampipe-plugin-googleworkspace/go.mod | 113 ++ steampipe-plugin-googleworkspace/go.sum | 1166 +++++++++++++++++ .../googleworkspace/connection_config.go | 25 + .../googleworkspace/not_found.go | 18 + .../googleworkspace/plugin.go | 48 + .../googleworkspace/service.go | 220 ++++ .../table_googleworkspace_calendar.go | 80 ++ .../table_googleworkspace_calendar_event.go | 407 ++++++ ...table_googleworkspace_calendar_my_event.go | 99 ++ .../table_googleworkspace_drive.go | 287 ++++ .../table_googleworkspace_drive_my_file.go | 506 +++++++ .../table_googleworkspace_gmail_draft.go | 231 ++++ .../table_googleworkspace_gmail_message.go | 261 ++++ .../table_googleworkspace_gmail_my_draft.go | 216 +++ .../table_googleworkspace_gmail_my_message.go | 219 ++++ ...table_googleworkspace_gmail_my_settings.go | 227 ++++ .../table_googleworkspace_gmail_settings.go | 241 ++++ .../table_googleworkspace_people_contact.go | 237 ++++ ...le_googleworkspace_people_contact_group.go | 150 +++ ...googleworkspace_people_directory_people.go | 87 ++ .../googleworkspace/utils.go | 51 + steampipe-plugin-googleworkspace/main.go | 11 + 73 files changed, 8040 insertions(+), 34 deletions(-) create mode 100644 steampipe-plugin-googleworkspace/.github/ISSUE_TEMPLATE/bug_report.md create mode 100644 steampipe-plugin-googleworkspace/.github/ISSUE_TEMPLATE/config.yml create mode 100644 steampipe-plugin-googleworkspace/.github/ISSUE_TEMPLATE/feature-request---new-table.md create mode 100644 steampipe-plugin-googleworkspace/.github/ISSUE_TEMPLATE/feature_request.md create mode 100644 steampipe-plugin-googleworkspace/.github/PULL_REQUEST_TEMPLATE.md create mode 100644 steampipe-plugin-googleworkspace/.github/dependabot.yml create mode 100644 steampipe-plugin-googleworkspace/.github/workflows/golangci-lint.yml create mode 100644 steampipe-plugin-googleworkspace/.github/workflows/registry-publish.yml create mode 100644 steampipe-plugin-googleworkspace/.github/workflows/stale.yml create mode 100644 steampipe-plugin-googleworkspace/.github/workflows/steampipe-anywhere.yml create mode 100644 steampipe-plugin-googleworkspace/.github/workflows/sync-labels.yml create mode 100644 steampipe-plugin-googleworkspace/.gitignore create mode 100644 steampipe-plugin-googleworkspace/.goreleaser.yml create mode 100644 steampipe-plugin-googleworkspace/CHANGELOG.md create mode 100644 steampipe-plugin-googleworkspace/LICENSE create mode 100644 steampipe-plugin-googleworkspace/Makefile create mode 100644 steampipe-plugin-googleworkspace/README.md create mode 100644 steampipe-plugin-googleworkspace/config/googleworkspace.spc create mode 100644 steampipe-plugin-googleworkspace/docs/LICENSE create mode 100644 steampipe-plugin-googleworkspace/docs/index.md create mode 100644 steampipe-plugin-googleworkspace/docs/tables/googleworkspace_calendar.md create mode 100644 steampipe-plugin-googleworkspace/docs/tables/googleworkspace_calendar_event.md create mode 100644 steampipe-plugin-googleworkspace/docs/tables/googleworkspace_calendar_my_event.md create mode 100644 steampipe-plugin-googleworkspace/docs/tables/googleworkspace_drive.md create mode 100644 steampipe-plugin-googleworkspace/docs/tables/googleworkspace_drive_my_file.md create mode 100644 steampipe-plugin-googleworkspace/docs/tables/googleworkspace_gmail_draft.md create mode 100644 steampipe-plugin-googleworkspace/docs/tables/googleworkspace_gmail_message.md create mode 100644 steampipe-plugin-googleworkspace/docs/tables/googleworkspace_gmail_my_draft.md create mode 100644 steampipe-plugin-googleworkspace/docs/tables/googleworkspace_gmail_my_message.md create mode 100644 steampipe-plugin-googleworkspace/docs/tables/googleworkspace_gmail_my_settings.md create mode 100644 steampipe-plugin-googleworkspace/docs/tables/googleworkspace_gmail_settings.md create mode 100644 steampipe-plugin-googleworkspace/docs/tables/googleworkspace_people_contact.md create mode 100644 steampipe-plugin-googleworkspace/docs/tables/googleworkspace_people_contact_group.md create mode 100644 steampipe-plugin-googleworkspace/docs/tables/googleworkspace_people_directory_people.md create mode 100644 steampipe-plugin-googleworkspace/go.mod create mode 100644 steampipe-plugin-googleworkspace/go.sum create mode 100644 steampipe-plugin-googleworkspace/googleworkspace/connection_config.go create mode 100644 steampipe-plugin-googleworkspace/googleworkspace/not_found.go create mode 100644 steampipe-plugin-googleworkspace/googleworkspace/plugin.go create mode 100644 steampipe-plugin-googleworkspace/googleworkspace/service.go create mode 100644 steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_calendar.go create mode 100644 steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_calendar_event.go create mode 100644 steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_calendar_my_event.go create mode 100644 steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_drive.go create mode 100644 steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_drive_my_file.go create mode 100644 steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_gmail_draft.go create mode 100644 steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_gmail_message.go create mode 100644 steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_gmail_my_draft.go create mode 100644 steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_gmail_my_message.go create mode 100644 steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_gmail_my_settings.go create mode 100644 steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_gmail_settings.go create mode 100644 steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_people_contact.go create mode 100644 steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_people_contact_group.go create mode 100644 steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_people_directory_people.go create mode 100644 steampipe-plugin-googleworkspace/googleworkspace/utils.go create mode 100644 steampipe-plugin-googleworkspace/main.go diff --git a/command/cmd/describer.go b/command/cmd/describer.go index 455354c3..7369a20b 100644 --- a/command/cmd/describer.go +++ b/command/cmd/describer.go @@ -4,11 +4,11 @@ import ( "encoding/json" "fmt" "github.com/google/uuid" - "github.com/opengovern/og-describer-template/pkg/describer" - model "github.com/opengovern/og-describer-template/pkg/sdk/models" - "github.com/opengovern/og-describer-template/provider" - "github.com/opengovern/og-describer-template/provider/configs" - "github.com/opengovern/og-describer-template/steampipe" + "github.com/opengovern/og-describer-googleworkspace/pkg/describer" + model "github.com/opengovern/og-describer-googleworkspace/pkg/sdk/models" + "github.com/opengovern/og-describer-googleworkspace/provider" + "github.com/opengovern/og-describer-googleworkspace/provider/configs" + "github.com/opengovern/og-describer-googleworkspace/steampipe" "github.com/opengovern/og-util/pkg/describe" "github.com/opengovern/og-util/pkg/es" "github.com/spf13/cobra" diff --git a/command/cmd/getDescriber.go b/command/cmd/getDescriber.go index 9595cdc1..0feafa4c 100644 --- a/command/cmd/getDescriber.go +++ b/command/cmd/getDescriber.go @@ -4,11 +4,11 @@ import ( "encoding/json" "fmt" "github.com/google/uuid" - "github.com/opengovern/og-describer-template/pkg/describer" - model "github.com/opengovern/og-describer-template/pkg/sdk/models" - "github.com/opengovern/og-describer-template/provider" - "github.com/opengovern/og-describer-template/provider/configs" - "github.com/opengovern/og-describer-template/steampipe" + "github.com/opengovern/og-describer-googleworkspace/pkg/describer" + model "github.com/opengovern/og-describer-googleworkspace/pkg/sdk/models" + "github.com/opengovern/og-describer-googleworkspace/provider" + "github.com/opengovern/og-describer-googleworkspace/provider/configs" + "github.com/opengovern/og-describer-googleworkspace/steampipe" "github.com/opengovern/og-util/pkg/describe" "github.com/opengovern/og-util/pkg/es" "github.com/spf13/cobra" diff --git a/command/main.go b/command/main.go index 771a4018..1656b512 100644 --- a/command/main.go +++ b/command/main.go @@ -3,7 +3,7 @@ Copyright © 2023 NAME HERE */ package main -import "github.com/opengovern/og-describer-template/command/cmd" +import "github.com/opengovern/og-describer-googleworkspace/command/cmd" func main() { cmd.Execute() diff --git a/go.mod b/go.mod index 71f5b702..c38e2c7f 100755 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/opengovern/og-describer-template +module github.com/opengovern/og-describer-googleworkspace go 1.22.0 diff --git a/main.go b/main.go index d8a24a54..5ee761ed 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,7 @@ package main import ( "context" "fmt" - "github.com/opengovern/og-describer-template/service" + "github.com/opengovern/og-describer-googleworkspace/service" "os" "os/signal" "syscall" diff --git a/pkg/describer/resource_sender.go b/pkg/describer/resource_sender.go index 82b068d2..c4d76e07 100755 --- a/pkg/describer/resource_sender.go +++ b/pkg/describer/resource_sender.go @@ -6,7 +6,7 @@ import ( "encoding/json" "errors" "fmt" - "github.com/opengovern/og-describer-template/provider/configs" + "github.com/opengovern/og-describer-googleworkspace/provider/configs" "github.com/opengovern/og-util/pkg/es" "github.com/opengovern/og-util/proto/src/golang" "go.uber.org/zap" diff --git a/pkg/describer/resources.go b/pkg/describer/resources.go index f8b16fe2..f1a03807 100755 --- a/pkg/describer/resources.go +++ b/pkg/describer/resources.go @@ -5,10 +5,10 @@ package describer import ( "context" "fmt" - model "github.com/opengovern/og-describer-template/pkg/sdk/models" - "github.com/opengovern/og-describer-template/provider" - "github.com/opengovern/og-describer-template/provider/configs" - "github.com/opengovern/og-describer-template/provider/describer" + model "github.com/opengovern/og-describer-googleworkspace/pkg/sdk/models" + "github.com/opengovern/og-describer-googleworkspace/provider" + "github.com/opengovern/og-describer-googleworkspace/provider/configs" + "github.com/opengovern/og-describer-googleworkspace/provider/describer" "github.com/opengovern/og-util/pkg/describe/enums" "go.uber.org/zap" "sort" diff --git a/pkg/describer/worker.go b/pkg/describer/worker.go index 9482604e..e8b5fb0b 100755 --- a/pkg/describer/worker.go +++ b/pkg/describer/worker.go @@ -5,10 +5,10 @@ import ( "encoding/json" "fmt" "github.com/go-errors/errors" - model "github.com/opengovern/og-describer-template/pkg/sdk/models" - "github.com/opengovern/og-describer-template/provider" - "github.com/opengovern/og-describer-template/provider/configs" - "github.com/opengovern/og-describer-template/steampipe" + model "github.com/opengovern/og-describer-googleworkspace/pkg/sdk/models" + "github.com/opengovern/og-describer-googleworkspace/provider" + "github.com/opengovern/og-describer-googleworkspace/provider/configs" + "github.com/opengovern/og-describer-googleworkspace/steampipe" describe2 "github.com/opengovern/og-util/pkg/describe" "github.com/opengovern/og-util/pkg/es" "github.com/opengovern/og-util/pkg/vault" diff --git a/pkg/sdk/models/resource_type.go b/pkg/sdk/models/resource_type.go index 51183a9c..5dc9e7fe 100644 --- a/pkg/sdk/models/resource_type.go +++ b/pkg/sdk/models/resource_type.go @@ -1,7 +1,7 @@ package models import ( - "github.com/opengovern/og-describer-template/provider/configs" + "github.com/opengovern/og-describer-googleworkspace/provider/configs" "github.com/opengovern/og-util/pkg/describe/enums" "github.com/opengovern/og-util/pkg/integration" "golang.org/x/net/context" diff --git a/pkg/sdk/nats.go b/pkg/sdk/nats.go index 90fd0be4..0a679ecc 100644 --- a/pkg/sdk/nats.go +++ b/pkg/sdk/nats.go @@ -4,8 +4,8 @@ import ( "context" "encoding/json" "errors" - "github.com/opengovern/og-describer-template/pkg/describer" - "github.com/opengovern/og-describer-template/provider/configs" + "github.com/opengovern/og-describer-googleworkspace/pkg/describer" + "github.com/opengovern/og-describer-googleworkspace/provider/configs" "os" "runtime" "time" diff --git a/pkg/sdk/runable/resource_type/main.go b/pkg/sdk/runable/resource_type/main.go index 695fe9bb..b3c0b1bc 100644 --- a/pkg/sdk/runable/resource_type/main.go +++ b/pkg/sdk/runable/resource_type/main.go @@ -5,7 +5,7 @@ import ( "encoding/json" "flag" "fmt" - "github.com/opengovern/og-describer-template/provider/configs" + "github.com/opengovern/og-describer-googleworkspace/provider/configs" "os" "sort" "strings" diff --git a/pkg/sdk/runable/steampipe_es_client_generator/main.go b/pkg/sdk/runable/steampipe_es_client_generator/main.go index b0155527..21b40604 100755 --- a/pkg/sdk/runable/steampipe_es_client_generator/main.go +++ b/pkg/sdk/runable/steampipe_es_client_generator/main.go @@ -5,7 +5,7 @@ import ( "encoding/json" "flag" "fmt" - "github.com/opengovern/og-describer-template/provider/configs" + "github.com/opengovern/og-describer-googleworkspace/provider/configs" "go/ast" "go/format" "go/parser" diff --git a/pkg/sdk/runable/steampipe_index_map/main.go b/pkg/sdk/runable/steampipe_index_map/main.go index f363a9e9..a746750b 100644 --- a/pkg/sdk/runable/steampipe_index_map/main.go +++ b/pkg/sdk/runable/steampipe_index_map/main.go @@ -5,7 +5,7 @@ import ( "encoding/json" "flag" "fmt" - "github.com/opengovern/og-describer-template/provider/configs" + "github.com/opengovern/og-describer-googleworkspace/provider/configs" "os" "strings" ) diff --git a/provider/credentials.go b/provider/credentials.go index 6487b132..a15d8ac3 100755 --- a/provider/credentials.go +++ b/provider/credentials.go @@ -2,8 +2,8 @@ package provider import ( "encoding/json" - model "github.com/opengovern/og-describer-template/pkg/sdk/models" - "github.com/opengovern/og-describer-template/provider/configs" + model "github.com/opengovern/og-describer-googleworkspace/pkg/sdk/models" + "github.com/opengovern/og-describer-googleworkspace/provider/configs" "github.com/opengovern/og-util/pkg/describe" ) diff --git a/provider/describer_wrapper.go b/provider/describer_wrapper.go index 6818e990..ad5bc9e4 100755 --- a/provider/describer_wrapper.go +++ b/provider/describer_wrapper.go @@ -1,8 +1,8 @@ package provider import ( - model "github.com/opengovern/og-describer-template/pkg/sdk/models" - "github.com/opengovern/og-describer-template/provider/configs" + model "github.com/opengovern/og-describer-googleworkspace/pkg/sdk/models" + "github.com/opengovern/og-describer-googleworkspace/provider/configs" "github.com/opengovern/og-util/pkg/describe/enums" "golang.org/x/net/context" ) diff --git a/provider/resource_types.go b/provider/resource_types.go index a7c49006..81450eb7 100644 --- a/provider/resource_types.go +++ b/provider/resource_types.go @@ -1,5 +1,5 @@ package provider -import model "github.com/opengovern/og-describer-template/pkg/sdk/models" +import model "github.com/opengovern/og-describer-googleworkspace/pkg/sdk/models" var ResourceTypes = map[string]model.ResourceType{} diff --git a/service/service.go b/service/service.go index 0044605e..7133a695 100644 --- a/service/service.go +++ b/service/service.go @@ -1,7 +1,7 @@ package service import ( - "github.com/opengovern/og-describer-template/pkg/sdk" + "github.com/opengovern/og-describer-googleworkspace/pkg/sdk" "github.com/spf13/cobra" "go.uber.org/zap" ) diff --git a/steampipe-plugin-googleworkspace/.github/ISSUE_TEMPLATE/bug_report.md b/steampipe-plugin-googleworkspace/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..4e89b19e --- /dev/null +++ b/steampipe-plugin-googleworkspace/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,26 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**Steampipe version (`steampipe -v`)** +Example: v0.3.0 + +**Plugin version (`steampipe plugin list`)** +Example: v0.5.0 + +**To reproduce** +Steps to reproduce the behavior (please include relevant code and/or commands). + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Additional context** +Add any other context about the problem here. diff --git a/steampipe-plugin-googleworkspace/.github/ISSUE_TEMPLATE/config.yml b/steampipe-plugin-googleworkspace/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..bef76500 --- /dev/null +++ b/steampipe-plugin-googleworkspace/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Questions + url: https://turbot.com/community/join + about: GitHub issues in this repository are only intended for bug reports and feature requests. Other issues will be closed. Please ask and answer questions through the Steampipe Slack community. + - name: Steampipe CLI Bug Reports and Feature Requests + url: https://github.com/turbot/steampipe/issues/new/choose + about: Steampipe CLI has its own codebase. Bug reports and feature requests for those pieces of functionality should be directed to that repository. \ No newline at end of file diff --git a/steampipe-plugin-googleworkspace/.github/ISSUE_TEMPLATE/feature-request---new-table.md b/steampipe-plugin-googleworkspace/.github/ISSUE_TEMPLATE/feature-request---new-table.md new file mode 100644 index 00000000..f1605de7 --- /dev/null +++ b/steampipe-plugin-googleworkspace/.github/ISSUE_TEMPLATE/feature-request---new-table.md @@ -0,0 +1,11 @@ +--- +name: Feature request - New table +about: Suggest a new table for this project +title: Add table googleworkspace__ +labels: enhancement, new table +assignees: '' + +--- + +**References** +Add any related links that will help us understand the resource, including vendor documentation, related GitHub issues, and Go SDK documentation. diff --git a/steampipe-plugin-googleworkspace/.github/ISSUE_TEMPLATE/feature_request.md b/steampipe-plugin-googleworkspace/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..11fc491e --- /dev/null +++ b/steampipe-plugin-googleworkspace/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/steampipe-plugin-googleworkspace/.github/PULL_REQUEST_TEMPLATE.md b/steampipe-plugin-googleworkspace/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..22bba7ab --- /dev/null +++ b/steampipe-plugin-googleworkspace/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,8 @@ +# Example query results +
+ Results + +``` +Add example SQL query results here (please include the input queries as well) +``` +
diff --git a/steampipe-plugin-googleworkspace/.github/dependabot.yml b/steampipe-plugin-googleworkspace/.github/dependabot.yml new file mode 100644 index 00000000..8450b99b --- /dev/null +++ b/steampipe-plugin-googleworkspace/.github/dependabot.yml @@ -0,0 +1,18 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "gomod" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" + pull-request-branch-name: + separator: "-" + assignees: + - "misraved" + - "madhushreeray30" + labels: + - "dependencies" diff --git a/steampipe-plugin-googleworkspace/.github/workflows/golangci-lint.yml b/steampipe-plugin-googleworkspace/.github/workflows/golangci-lint.yml new file mode 100644 index 00000000..89d69978 --- /dev/null +++ b/steampipe-plugin-googleworkspace/.github/workflows/golangci-lint.yml @@ -0,0 +1,12 @@ +name: golangci-lint +on: + push: + tags: + - v* + branches: + - main + pull_request: + +jobs: + golangci_lint_workflow: + uses: turbot/steampipe-workflows/.github/workflows/golangci-lint.yml@main diff --git a/steampipe-plugin-googleworkspace/.github/workflows/registry-publish.yml b/steampipe-plugin-googleworkspace/.github/workflows/registry-publish.yml new file mode 100644 index 00000000..08f3d594 --- /dev/null +++ b/steampipe-plugin-googleworkspace/.github/workflows/registry-publish.yml @@ -0,0 +1,13 @@ +name: Build and Deploy OCI Image + +on: + push: + tags: + - 'v*' + +jobs: + registry_publish_workflow_ghcr: + uses: turbot/steampipe-workflows/.github/workflows/registry-publish-ghcr.yml@main + secrets: inherit + with: + releaseTimeout: 60m diff --git a/steampipe-plugin-googleworkspace/.github/workflows/stale.yml b/steampipe-plugin-googleworkspace/.github/workflows/stale.yml new file mode 100644 index 00000000..6a30f3b3 --- /dev/null +++ b/steampipe-plugin-googleworkspace/.github/workflows/stale.yml @@ -0,0 +1,17 @@ +name: Stale Issues and PRs +on: + schedule: + - cron: "30 23 * * *" + workflow_dispatch: + inputs: + dryRun: + description: Set to true for a dry run + required: false + default: "false" + type: string + +jobs: + stale_workflow: + uses: turbot/steampipe-workflows/.github/workflows/stale.yml@main + with: + dryRun: ${{ github.event.inputs.dryRun }} diff --git a/steampipe-plugin-googleworkspace/.github/workflows/steampipe-anywhere.yml b/steampipe-plugin-googleworkspace/.github/workflows/steampipe-anywhere.yml new file mode 100644 index 00000000..05092d63 --- /dev/null +++ b/steampipe-plugin-googleworkspace/.github/workflows/steampipe-anywhere.yml @@ -0,0 +1,12 @@ +name: Release Steampipe Anywhere Components + +on: + push: + tags: + - 'v*' + + +jobs: + anywhere_publish_workflow: + uses: turbot/steampipe-workflows/.github/workflows/steampipe-anywhere.yml@main + secrets: inherit diff --git a/steampipe-plugin-googleworkspace/.github/workflows/sync-labels.yml b/steampipe-plugin-googleworkspace/.github/workflows/sync-labels.yml new file mode 100644 index 00000000..6a2e42a5 --- /dev/null +++ b/steampipe-plugin-googleworkspace/.github/workflows/sync-labels.yml @@ -0,0 +1,9 @@ +name: Sync Labels +on: + schedule: + - cron: "30 22 * * 1" + workflow_dispatch: + +jobs: + sync_labels_workflow: + uses: turbot/steampipe-workflows/.github/workflows/sync-labels.yml@main diff --git a/steampipe-plugin-googleworkspace/.gitignore b/steampipe-plugin-googleworkspace/.gitignore new file mode 100644 index 00000000..66fd13c9 --- /dev/null +++ b/steampipe-plugin-googleworkspace/.gitignore @@ -0,0 +1,15 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ diff --git a/steampipe-plugin-googleworkspace/.goreleaser.yml b/steampipe-plugin-googleworkspace/.goreleaser.yml new file mode 100644 index 00000000..63891a35 --- /dev/null +++ b/steampipe-plugin-googleworkspace/.goreleaser.yml @@ -0,0 +1,37 @@ +# This is an example goreleaser.yaml file with some sane defaults. +# Make sure to check the documentation at http://goreleaser.com +before: + hooks: + - go mod tidy +builds: + - env: + - CGO_ENABLED=0 + - GO111MODULE=on + - GOPRIVATE=github.com/turbot + goos: + - linux + - darwin + + goarch: + - amd64 + - arm64 + + id: "steampipe" + binary: "{{ .ProjectName }}.plugin" + flags: + - -tags=netgo + +archives: + - format: gz + name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}" + files: + - none* +checksum: + name_template: "{{ .ProjectName }}_{{ .Version }}_SHA256SUMS" + algorithm: sha256 +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" diff --git a/steampipe-plugin-googleworkspace/CHANGELOG.md b/steampipe-plugin-googleworkspace/CHANGELOG.md new file mode 100644 index 00000000..881ff8a7 --- /dev/null +++ b/steampipe-plugin-googleworkspace/CHANGELOG.md @@ -0,0 +1,138 @@ +## v1.0.0 [2024-10-22] + +There are no significant changes in this plugin version; it has been released to align with [Steampipe's v1.0.0](https://steampipe.io/changelog/steampipe-cli-v1-0-0) release. This plugin adheres to [semantic versioning](https://semver.org/#semantic-versioning-specification-semver), ensuring backward compatibility within each major version. + +_Dependencies_ + +- Recompiled plugin with Go version `1.22`. ([#82](https://github.com/turbot/steampipe-plugin-googleworkspace/pull/82)) +- Recompiled plugin with [steampipe-plugin-sdk v5.10.4](https://github.com/turbot/steampipe-plugin-sdk/blob/develop/CHANGELOG.md#v5104-2024-08-29) that fixes logging in the plugin export tool. ([#82](https://github.com/turbot/steampipe-plugin-googleworkspace/pull/82)) + +## v0.8.0 [2023-12-12] + +_What's new?_ + +- The plugin can now be downloaded and used with the [Steampipe CLI](https://steampipe.io/docs), as a [Postgres FDW](https://steampipe.io/docs/steampipe_postgres/overview), as a [SQLite extension](https://steampipe.io/docs//steampipe_sqlite/overview) and as a standalone [exporter](https://steampipe.io/docs/steampipe_export/overview). ([#77](https://github.com/turbot/steampipe-plugin-googleworkspace/pull/77)) +- The table docs have been updated to provide corresponding example queries for Postgres FDW and SQLite extension. ([#77](https://github.com/turbot/steampipe-plugin-googleworkspace/pull/77)) +- Docs license updated to match Steampipe [CC BY-NC-ND license](https://github.com/turbot/steampipe-plugin-googleworkspace/blob/main/docs/LICENSE). ([#77](https://github.com/turbot/steampipe-plugin-googleworkspace/pull/77)) + +_Dependencies_ + +- Recompiled plugin with [steampipe-plugin-sdk v5.8.0](https://github.com/turbot/steampipe-plugin-sdk/blob/main/CHANGELOG.md#v580-2023-12-11) that includes plugin server encapsulation for in-process and GRPC usage, adding Steampipe Plugin SDK version to `_ctx` column, and fixing connection and potential divide-by-zero bugs. ([#76](https://github.com/turbot/steampipe-plugin-googleworkspace/pull/76)) + +## v0.7.1 [2023-10-05] + +_Dependencies_ + +- Recompiled plugin with [steampipe-plugin-sdk v5.6.2](https://github.com/turbot/steampipe-plugin-sdk/blob/main/CHANGELOG.md#v562-2023-10-03) which prevents nil pointer reference errors for implicit hydrate configs. ([#56](https://github.com/turbot/steampipe-plugin-googleworkspace/pull/56)) + +## v0.7.0 [2023-10-02] + +_Dependencies_ + +- Upgraded to [steampipe-plugin-sdk v5.6.1](https://github.com/turbot/steampipe-plugin-sdk/blob/main/CHANGELOG.md#v561-2023-09-29) with support for rate limiters. ([#51](https://github.com/turbot/steampipe-plugin-googleworkspace/pull/51)) +- Recompiled plugin with Go version `1.21`. ([#51](https://github.com/turbot/steampipe-plugin-googleworkspace/pull/51)) + +## v0.6.0 [2023-03-23] + +_Dependencies_ + +- Recompiled plugin with [steampipe-plugin-sdk v5.3.0](https://github.com/turbot/steampipe-plugin-sdk/blob/main/CHANGELOG.md#v530-2023-03-16) which includes fixes for query cache pending item mechanism and aggregator connections not working for dynamic tables. ([#40](https://github.com/turbot/steampipe-plugin-googleworkspace/pull/40)) + +## v0.5.0 [2022-09-29] + +_Dependencies_ + +- Recompiled plugin with [steampipe-plugin-sdk v4.1.7](https://github.com/turbot/steampipe-plugin-sdk/blob/main/CHANGELOG.md#v417-2022-09-08) which includes several caching and memory management improvements. ([#36](https://github.com/turbot/steampipe-plugin-googleworkspace/pull/36)) +- Recompiled plugin with Go version `1.19`. ([#36](https://github.com/turbot/steampipe-plugin-googleworkspace/pull/36)) + +## v0.4.0 [2022-07-21] + +_Bug fixes_ + +- Fixed the `GetConfig` max concurrency configuration in the `googleworkspace_gmail_message` and the `googleworkspace_gmail_my_message` tables to resolve the plugin validation errors. ([#34](https://github.com/turbot/steampipe-plugin-googleworkspace/pull/34)) + +_Dependencies_ + +- Recompiled plugin with [steampipe-plugin-sdk v3.3.2](https://github.com/turbot/steampipe-plugin-sdk/blob/main/CHANGELOG.md#v332--2022-07-11) which includes several caching fixes. ([#34](https://github.com/turbot/steampipe-plugin-googleworkspace/pull/34)) + +## v0.3.0 [2022-04-27] + +_Enhancements_ + +- Added support for native Linux ARM and Mac M1 builds. ([#31](https://github.com/turbot/steampipe-plugin-googleworkspace/pull/31)) +- Recompiled plugin with [steampipe-plugin-sdk v3.1.0](https://github.com/turbot/steampipe-plugin-sdk/blob/main/CHANGELOG.md#v310--2022-03-30) and Go version `1.18`. ([#30](https://github.com/turbot/steampipe-plugin-googleworkspace/pull/30)) + +## v0.2.1 [2022-04-14] + +_Bug fixes_ + +- Fixed links in documentation for configuring OAuth client authentication. + +## v0.2.0 [2022-01-31] + +_What's new?_ + +- Added: The `credentials` argument can now be specified in the configuration file to pass in either the path to or the contents of a service account key file in JSON format ([#25](https://github.com/turbot/steampipe-plugin-googleworkspace/pull/25)) + +_Deprecated_ + +- The `credential_file` argument in the configuration file is now deprecated and will be removed in the next major version. We recommend using the `credentials` argument instead, which can take the same file path as the `credential_file` argument. ([#25](https://github.com/turbot/steampipe-plugin-googleworkspace/pull/25)) + +## v0.1.0 [2021-12-08] + +_Enhancements_ + +- Recompiled plugin with Go version 1.17 ([#22](https://github.com/turbot/steampipe-plugin-googleworkspace/pull/22)) +- Recompiled plugin with [steampipe-plugin-sdk v1.8.2](https://github.com/turbot/steampipe-plugin-sdk/blob/main/CHANGELOG.md#v182--2021-11-22) ([#19](https://github.com/turbot/steampipe-plugin-googleworkspace/pull/19)) + +## v0.0.3 [2021-10-20] + +_Bug fixes_ + +- Fixed: All tables now return the service API disabled error directly instead of returning empty rows + +## v0.0.2 [2021-09-17] + +_What's new?_ + +- Added: Support for OAuth 2.0 authentication ([#11](https://github.com/turbot/steampipe-plugin-googleworkspace/pull/11)) +- Added: Additional optional key columns and better filtering capabilities to the following tables: + - googleworkspace_calendar_event + - googleworkspace_calendar_my_event + - googleworkspace_drive + - googleworkspace_drive_my_file + - googleworkspace_gmail_draft + - googleworkspace_gmail_my_draft + - googleworkspace_gmail_my_message + - googleworkspace_people_contact + - googleworkspace_people_contact_group + - googleworkspace_people_directory_people + +_Enhancements_ + +- Updated: Improve context cancellation handling in all tables ([#11](https://github.com/turbot/steampipe-plugin-googleworkspace/pull/11)) + +_Bug fixes_ + +- Fixed: Querying the `delegates` column in the `googleworkspace_gmail_my_settings` and `googleworkspace_gmail_settings` tables when using OAuth authentication now returns `null` instead of an error ([#11](https://github.com/turbot/steampipe-plugin-googleworkspace/pull/11)) + +## v0.0.1 [2021-08-26] + +_What's new?_ + +- New tables added + + - [googleworkspace_calendar](https://hub.steampipe.io/plugins/turbot/googleworkspace/tables/googleworkspace_calendar) + - [googleworkspace_calendar_event](https://hub.steampipe.io/plugins/turbot/googleworkspace/tables/googleworkspace_calendar_event) + - [googleworkspace_calendar_my_event](https://hub.steampipe.io/plugins/turbot/googleworkspace/tables/googleworkspace_calendar_my_event) + - [googleworkspace_drive](https://hub.steampipe.io/plugins/turbot/googleworkspace/tables/googleworkspace_drive) + - [googleworkspace_drive_my_file](https://hub.steampipe.io/plugins/turbot/googleworkspace/tables/googleworkspace_drive_my_file) + - [googleworkspace_gmail_draft](https://hub.steampipe.io/plugins/turbot/googleworkspace/tables/googleworkspace_gmail_draft) + - [googleworkspace_gmail_message](https://hub.steampipe.io/plugins/turbot/googleworkspace/tables/googleworkspace_gmail_message) + - [googleworkspace_gmail_my_draft](https://hub.steampipe.io/plugins/turbot/googleworkspace/tables/googleworkspace_gmail_my_draft) + - [googleworkspace_gmail_my_message](https://hub.steampipe.io/plugins/turbot/googleworkspace/tables/googleworkspace_gmail_my_message) + - [googleworkspace_gmail_my_settings](https://hub.steampipe.io/plugins/turbot/googleworkspace/tables/googleworkspace_gmail_my_settings) + - [googleworkspace_gmail_settings](https://hub.steampipe.io/plugins/turbot/googleworkspace/tables/googleworkspace_gmail_settings) + - [googleworkspace_people_contact](https://hub.steampipe.io/plugins/turbot/googleworkspace/tables/googleworkspace_people_contact) + - [googleworkspace_people_contact_group](https://hub.steampipe.io/plugins/turbot/googleworkspace/tables/googleworkspace_people_contact_group) + - [googleworkspace_people_directory_people](https://hub.steampipe.io/plugins/turbot/googleworkspace/tables/googleworkspace_people_directory_people) diff --git a/steampipe-plugin-googleworkspace/LICENSE b/steampipe-plugin-googleworkspace/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/steampipe-plugin-googleworkspace/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/steampipe-plugin-googleworkspace/Makefile b/steampipe-plugin-googleworkspace/Makefile new file mode 100644 index 00000000..b611e34d --- /dev/null +++ b/steampipe-plugin-googleworkspace/Makefile @@ -0,0 +1,4 @@ +STEAMPIPE_INSTALL_DIR ?= ~/.steampipe +BUILD_TAGS = netgo +install: + go build -o $(STEAMPIPE_INSTALL_DIR)/plugins/hub.steampipe.io/plugins/turbot/googleworkspace@latest/steampipe-plugin-googleworkspace.plugin -tags "${BUILD_TAGS}" *.go diff --git a/steampipe-plugin-googleworkspace/README.md b/steampipe-plugin-googleworkspace/README.md new file mode 100644 index 00000000..3cedc919 --- /dev/null +++ b/steampipe-plugin-googleworkspace/README.md @@ -0,0 +1,101 @@ +![image](https://hub.steampipe.io/images/plugins/turbot/googleworkspace-social-graphic.png) + +# Google Workspace Plugin for Steampipe + +Use SQL to query users, groups, org units and more from your Google Workspace. + +- **[Get started →](https://hub.steampipe.io/plugins/turbot/googleworkspace)** +- Documentation: [Table definitions & examples](https://hub.steampipe.io/plugins/turbot/googleworkspace/tables) +- Community: [Join #steampipe on Slack →](https://turbot.com/community/join) +- Get involved: [Issues](https://github.com/turbot/steampipe-plugin-googleworkspace/issues) + +## Quick start + +Install the plugin with [Steampipe](https://steampipe.io): + +```shell +steampipe plugin install googleworkspace +``` + +Configure your [credentials](https://hub.steampipe.io/plugins/turbot/googleworkspace#credentials) and [config file](https://hub.steampipe.io/plugins/turbot/googleworkspace#configuration). + +Run a query: + +```sql +select + summary, + hangout_link, + start_time, + end_time +from + googleworkspace_calendar_my_event +where + start_time > now()::timestamp + and end_time < ('now'::timestamp + interval '1 day'); +``` + +## Engines + +This plugin is available for the following engines: + +| Engine | Description +|---------------|------------------------------------------ +| [Steampipe](https://steampipe.io/docs) | The Steampipe CLI exposes APIs and services as a high-performance relational database, giving you the ability to write SQL-based queries to explore dynamic data. Mods extend Steampipe's capabilities with dashboards, reports, and controls built with simple HCL. The Steampipe CLI is a turnkey solution that includes its own Postgres database, plugin management, and mod support. +| [Postgres FDW](https://steampipe.io/docs/steampipe_postgres/overview) | Steampipe Postgres FDWs are native Postgres Foreign Data Wrappers that translate APIs to foreign tables. Unlike Steampipe CLI, which ships with its own Postgres server instance, the Steampipe Postgres FDWs can be installed in any supported Postgres database version. +| [SQLite Extension](https://steampipe.io/docs/steampipe_sqlite/overview) | Steampipe SQLite Extensions provide SQLite virtual tables that translate your queries into API calls, transparently fetching information from your API or service as you request it. +| [Export](https://steampipe.io/docs/steampipe_export/overview) | Steampipe Plugin Exporters provide a flexible mechanism for exporting information from cloud services and APIs. Each exporter is a stand-alone binary that allows you to extract data using Steampipe plugins without a database. +| [Turbot Pipes](https://turbot.com/pipes/docs) | Turbot Pipes is the only intelligence, automation & security platform built specifically for DevOps. Pipes provide hosted Steampipe database instances, shared dashboards, snapshots, and more. + +## Developing + +Prerequisites: + +- [Steampipe](https://steampipe.io/downloads) +- [Golang](https://golang.org/doc/install) + +Clone: + +```sh +git clone https://github.com/turbot/steampipe-plugin-googleworkspace.git +cd steampipe-plugin-googleworkspace +``` + +Build, which automatically installs the new version to your `~/.steampipe/plugins` directory: + +``` +make +``` + +Configure the plugin: + +``` +cp config/* ~/.steampipe/config +vi ~/.steampipe/config/googleworkspace.spc +``` + +Try it! + +``` +steampipe query +> .inspect googleworkspace +``` + +Further reading: + +- [Writing plugins](https://steampipe.io/docs/develop/writing-plugins) +- [Writing your first table](https://steampipe.io/docs/develop/writing-your-first-table) + +## Open Source & Contributing + +This repository is published under the [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) (source code) and [CC BY-NC-ND](https://creativecommons.org/licenses/by-nc-nd/2.0/) (docs) licenses. Please see our [code of conduct](https://github.com/turbot/.github/blob/main/CODE_OF_CONDUCT.md). We look forward to collaborating with you! + +[Steampipe](https://steampipe.io) is a product produced from this open source software, exclusively by [Turbot HQ, Inc](https://turbot.com). It is distributed under our commercial terms. Others are allowed to make their own distribution of the software, but cannot use any of the Turbot trademarks, cloud services, etc. You can learn more in our [Open Source FAQ](https://turbot.com/open-source). + +## Get Involved + +**[Join #steampipe on Slack →](https://turbot.com/community/join)** + +Want to help but don't know where to start? Pick up one of the `help wanted` issues: + +- [Steampipe](https://github.com/turbot/steampipe/labels/help%20wanted) +- [Google Workspace Plugin](https://github.com/turbot/steampipe-plugin-googleworkspace/labels/help%20wanted) diff --git a/steampipe-plugin-googleworkspace/config/googleworkspace.spc b/steampipe-plugin-googleworkspace/config/googleworkspace.spc new file mode 100644 index 00000000..628218e8 --- /dev/null +++ b/steampipe-plugin-googleworkspace/config/googleworkspace.spc @@ -0,0 +1,24 @@ +connection "googleworkspace" { + plugin = "googleworkspace" + + # You may connect to Google Workspace using more than one option: + # 1. To authenticate using domain-wide delegation, specify a service account credential file and the user email for impersonation + # `credentials` - Either the path to a JSON credential file that contains Google application credentials, + # or the contents of a service account key file in JSON format. If `credentials` is not specified in a connection, + # credentials will be loaded from: + # - The path specified in the `GOOGLE_APPLICATION_CREDENTIALS` environment variable, if set; otherwise + # - The standard location (`~/.config/gcloud/application_default_credentials.json`) + # - The path specified for the credentials.json file ("/path/to/my/creds.json") + # credentials = "~/.config/gcloud/application_default_credentials.json" + + # `impersonated_user_email` - The email (string) of the user which should be impersonated. Needs permissions to access the Admin APIs. + # `impersonated_user_email` must be set, since the service account needs to impersonate a user with Admin API permissions to access the workspace services. + # impersonated_user_email = "username@domain.com" + + # 2. To authenticate using OAuth 2.0, specify a client secret file + # `token_path` - The path to a JSON credential file that contains Google application credentials. + # If `token_path` is not specified in a connection, credentials will be loaded from: + # - The path specified in the `GOOGLE_APPLICATION_CREDENTIALS` environment variable, if set; otherwise + # - The standard location (`~/.config/gcloud/application_default_credentials.json`) + # token_path = "~/.config/gcloud/application_default_credentials.json" +} diff --git a/steampipe-plugin-googleworkspace/docs/LICENSE b/steampipe-plugin-googleworkspace/docs/LICENSE new file mode 100644 index 00000000..236ac28a --- /dev/null +++ b/steampipe-plugin-googleworkspace/docs/LICENSE @@ -0,0 +1,402 @@ +Attribution-NonCommercial-NoDerivatives 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 +International Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-NonCommercial-NoDerivatives 4.0 International Public +License ("Public License"). To the extent this Public License may be +interpreted as a contract, You are granted the Licensed Rights in +consideration of Your acceptance of these terms and conditions, and the +Licensor grants You such rights in consideration of benefits the +Licensor receives from making the Licensed Material available under +these terms and conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + c. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + d. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + e. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + f. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + g. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + h. NonCommercial means not primarily intended for or directed towards + commercial advantage or monetary compensation. For purposes of + this Public License, the exchange of the Licensed Material for + other material subject to Copyright and Similar Rights by digital + file-sharing or similar means is NonCommercial provided there is + no payment of monetary compensation in connection with the + exchange. + + i. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + j. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + k. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part, for NonCommercial purposes only; and + + b. produce and reproduce, but not Share, Adapted Material + for NonCommercial purposes only. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties, including when + the Licensed Material is used other than for NonCommercial + purposes. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material, You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + For the avoidance of doubt, You do not have permission under + this Public License to Share Adapted Material. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database for NonCommercial purposes + only and provided You do not Share Adapted Material; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material; and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. \ No newline at end of file diff --git a/steampipe-plugin-googleworkspace/docs/index.md b/steampipe-plugin-googleworkspace/docs/index.md new file mode 100644 index 00000000..204e602a --- /dev/null +++ b/steampipe-plugin-googleworkspace/docs/index.md @@ -0,0 +1,120 @@ +--- +organization: Turbot +category: ["saas"] +icon_url: "/images/plugins/turbot/googleworkspace.svg" +brand_color: "#ea4335" +display_name: "Google Workspace" +short_name: "googleworkspace" +description: "Steampipe plugin for querying users, groups, org units and more from your Google Workspace." +og_description: "Query Google Workspace with SQL! Open source CLI. No DB required." +og_image: "/images/plugins/turbot/googleworkspace-social-graphic.png" +engines: ["steampipe", "sqlite", "postgres", "export"] +--- + +# Google Workspace + Steampipe + +[Google Workspace](https://workspace.google.com) is a collection of cloud computing, productivity and collaboration tools, software and products developed and marketed by Google. + +[Steampipe](https://steampipe.io) is an open-source zero-ETL engine to instantly query cloud APIs using SQL. + +For example: + +```sql +select + summary, + hangout_link, + start_time, + end_time +from + googleworkspace_calendar_my_event +where + start_time > now()::timestamp + and end_time < ('now'::timestamp + interval '1 day'); +``` + +``` ++----------------+--------------------------------------+---------------------+---------------------+ +| summary | hangout_link | start_time | end_time | ++----------------+--------------------------------------+---------------------+---------------------+ +| Product Review | https://meet.google.com/ris-zooa-rxo | 2021-08-18 12:30:00 | 2021-08-18 13:00:00 | ++----------------+--------------------------------------+---------------------+---------------------+ +``` + +## Documentation + +- **[Table definitions & examples →](/plugins/turbot/googleworkspace/tables)** + +## Get started + +### Install + +Download and install the latest Google Workspace plugin: + +```bash +steampipe plugin install googleworkspace +``` + +### Credentials + +| Item | Description | +| :---------- | :-----------| +| APIs | 1. Go to the [Google API Console](https://console.cloud.google.com/apis/dashboard).
2. Select the project that contains your credentials.
3. Click `Enable APIs and Services`.
4. Enable: `Google Calendar API`, `Google Drive API`, `Gmail API`, `Google People API`. +| Credentials | 1. To use **domain-wide delegation**, generate your [service account and credentials](https://developers.google.com/admin-sdk/directory/v1/guides/delegation#create_the_service_account_and_credentials) and [delegate domain-wide authority to your service account](https://developers.google.com/admin-sdk/directory/v1/guides/delegation#delegate_domain-wide_authority_to_your_service_account). Enter the following OAuth 2.0 scopes for the services that the service account can access:
`https://www.googleapis.com/auth/calendar.readonly`,
`https://www.googleapis.com/auth/contacts.readonly`,
`https://www.googleapis.com/auth/contacts.other.readonly`,
`https://www.googleapis.com/auth/directory.readonly`,
`https://www.googleapis.com/auth/drive.readonly`,
`https://www.googleapis.com/auth/gmail.readonly`
2. To use **OAuth client**, configure your [credentials](#authenticate-using-oauth-client). | +| Radius | Each connection represents a single Google Workspace account. | +| Resolution | 1. Credentials from the JSON file specified by the `credentials` parameter in your Steampipe config.
2. Credentials from the JSON file specified by the `token_path` parameter in your Steampipe config.
3. Credentials from the default json file location (`~/.config/gcloud/application_default_credentials.json`). | + +### Configuration + +Installing the latest googleworkspace plugin will create a config file (`~/.steampipe/config/googleworkspace.spc`) with a single connection named `googleworkspace`: + +```hcl +connection "googleworkspace" { + plugin = "googleworkspace" + + # You may connect to Google Workspace using more than one option: + # 1. To authenticate using domain-wide delegation, specify a service account credential file and the user email for impersonation + # `credentials` - Either the path to a JSON credential file that contains Google application credentials, + # or the contents of a service account key file in JSON format. If `credentials` is not specified in a connection, + # credentials will be loaded from: + # - The path specified in the `GOOGLE_APPLICATION_CREDENTIALS` environment variable, if set; otherwise + # - The standard location (`~/.config/gcloud/application_default_credentials.json`) + # - The path specified for the credentials.json file ("/path/to/my/creds.json") + # credentials = "~/.config/gcloud/application_default_credentials.json" + # `impersonated_user_email` - The email (string) of the user which should be impersonated. Needs permissions to access the Admin APIs. + # `impersonated_user_email` must be set, since the service account needs to impersonate a user with Admin API permissions to access the workspace services. + # impersonated_user_email = "username@domain.com" + + # 2. To authenticate using OAuth 2.0, specify a client secret file + # `token_path` - The path to a JSON credential file that contains Google application credentials. + # If `token_path` is not specified in a connection, credentials will be loaded from: + # - The path specified in the `GOOGLE_APPLICATION_CREDENTIALS` environment variable, if set; otherwise + # - The standard location (`~/.config/gcloud/application_default_credentials.json`) + # token_path = "~/.config/gcloud/application_default_credentials.json" +} +``` + +## Advanced configuration options + +### Authenticate using OAuth client + +You can use client secret credentials to protect the user's data by only granting tokens to authorized requestors. Use following steps to configure credentials: + +- [Configure the OAuth consent screen](https://developers.google.com/workspace/guides/configure-oauth-consent). +- [Create an OAuth client ID credential](https://developers.google.com/workspace/guides/create-credentials#desktop-app) with the application type `Desktop app`, and download the client secret JSON file. +- Wherever you have the [Google Cloud SDK](https://cloud.google.com/sdk/docs/install) installed, run the following command with the correct client secret JSON file parameters: + + ```sh + gcloud auth application-default login \ + --client-id-file=client_secret.json \ + --scopes="\ + https://www.googleapis.com/auth/calendar.readonly,\ + https://www.googleapis.com/auth/contacts.other.readonly,\ + https://www.googleapis.com/auth/contacts.readonly,\ + https://www.googleapis.com/auth/directory.readonly,\ + https://www.googleapis.com/auth/drive.readonly,\ + https://www.googleapis.com/auth/gmail.readonly" + ``` + +- In the browser window that just opened, authenticate as the user you would like to make the API calls through. +- Review the output for the location of the **Application Default Credentials** file, which usually appears following the text `Credentials saved to file:`. +- Set the **Application Default Credentials** filepath in the Steampipe config `token_path` or in the `GOOGLE_APPLICATION_CREDENTIALS` environment variable. diff --git a/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_calendar.md b/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_calendar.md new file mode 100644 index 00000000..6ab1880a --- /dev/null +++ b/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_calendar.md @@ -0,0 +1,42 @@ +--- +title: "Steampipe Table: googleworkspace_calendar - Query Google Workspace Calendars using SQL" +description: "Allows users to query Google Workspace Calendars, specifically the details of each calendar such as the summary, description, location, timezone, and more." +--- + +# Table: googleworkspace_calendar - Query Google Workspace Calendars using SQL + +Google Workspace Calendar is a time-management and scheduling service developed by Google. It enables users to create and edit events, manage multiple calendars, and share calendars with others. Google Workspace Calendar is fully integrated with other Google services, providing seamless scheduling and collaboration capabilities. + +## Table Usage Guide + +The `googleworkspace_calendar` table provides insights into calendars within Google Workspace. As a system administrator or IT professional, explore calendar-specific details through this table, including the summary, description, location, and timezone. Utilize it to manage and monitor the usage of calendars, such as those shared with many users, the timezone settings of each calendar, and the description and summary details. + +**Important Notes** +- You must specify the `id` in the `where` or join clause (`where id=`, `join googleworkspace_calendar c on c.id=`) to query this table. + +## Examples + +### Basic info +Explore the basic details of a specific user's Google Workspace Calendar. This can help in understanding the user's time zone and other relevant information to enhance scheduling and coordination. + +```sql+postgres +select + summary, + id, + timezone +from + googleworkspace_calendar +where + id = 'user@domain.com'; +``` + +```sql+sqlite +select + summary, + id, + timezone +from + googleworkspace_calendar +where + id = 'user@domain.com'; +``` \ No newline at end of file diff --git a/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_calendar_event.md b/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_calendar_event.md new file mode 100644 index 00000000..67129005 --- /dev/null +++ b/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_calendar_event.md @@ -0,0 +1,211 @@ +--- +title: "Steampipe Table: googleworkspace_calendar_event - Query Google Workspace Calendar Events using SQL" +description: "Allows users to query Google Workspace Calendar Events, specifically the event details, providing insights into event schedules, attendees, and more." +--- + +# Table: googleworkspace_calendar_event - Query Google Workspace Calendar Events using SQL + +Google Workspace Calendar Events is a feature within Google Workspace that allows you to schedule, manage, and track events. It provides a centralized way to manage events for various Google Workspace users, including details about the event, attendees, and more. Google Workspace Calendar Events helps you stay informed about the event schedules and take appropriate actions when needed. + +## Table Usage Guide + +The `googleworkspace_calendar_event` table provides insights into Calendar Events within Google Workspace. As an IT administrator, explore event-specific details through this table, including event schedules, attendees, and associated metadata. Utilize it to uncover information about events, such as those with multiple attendees, the status of the attendees, and the verification of event details. + +**Important Notes** +- You must specify the `calendar_id` in the `where` or join clause (`where calendar_id=`, `join googleworkspace_calendar_event e on e.calendar_id=`) to query this table. + +## Examples + +### Basic info +Gain insights into upcoming events on a specific Google Workspace calendar. This query helps in planning and scheduling by providing details like the event summary, hangout link, start time, and end time of the ten soonest events. + +```sql+postgres +select + calendar_id, + summary, + hangout_link, + start_time, + end_time +from + googleworkspace_calendar_event +where + calendar_id = 'user@domain.com' +order by start_time +limit 10; +``` + +```sql+sqlite +select + calendar_id, + summary, + hangout_link, + start_time, + end_time +from + googleworkspace_calendar_event +where + calendar_id = 'user@domain.com' +order by start_time +limit 10; +``` + +### List events scheduled in next 4 days +Identify upcoming events in your company's calendar for the next four days. This allows you to stay updated with the scheduled activities, their timings, and corresponding links, thereby aiding in effective time management and planning. + +```sql+postgres +select + summary, + hangout_link, + start_time, + end_time +from + googleworkspace_calendar_event +where + calendar_id = 'company-calendar@domain.com' + and start_time >= current_date + and start_time <= (current_date + interval '4 days') +order by start_time; +``` + +```sql+sqlite +select + summary, + hangout_link, + start_time, + end_time +from + googleworkspace_calendar_event +where + calendar_id = 'company-calendar@domain.com' + and start_time >= date('now') + and start_time <= date('now', '+4 days') +order by start_time; +``` + +### List events scheduled in current month +Explore the scheduled events for the current month to stay updated on important dates and activities. This is beneficial in managing your time and ensuring no important event is overlooked. + +```sql+postgres +select + summary, + hangout_link, + start_time, + end_time +from + googleworkspace_calendar_event +where + calendar_id = 'company-calendar@domain.com' + and start_time >= date_trunc('month', current_date) + and start_time <= date_trunc('month', current_date) + interval '1 month' +order by start_time; +``` + +```sql+sqlite +select + summary, + hangout_link, + start_time, + end_time +from + googleworkspace_calendar_event +where + calendar_id = 'company-calendar@domain.com' + and start_time >= date('now','start of month') + and start_time <= date('now','start of month','+1 month') +order by start_time; +``` + +### List events scheduled in current week +Explore which company events are scheduled for the upcoming week. This query is useful for keeping track of upcoming events and meetings in your organization. + +```sql+postgres +select + summary, + hangout_link, + start_time, + end_time +from + googleworkspace_calendar_event +where + calendar_id = 'company-calendar@domain.com' + and start_time >= date_trunc('week', current_date) + and start_time < (date_trunc('week', current_date) + interval '7 days') +order by start_time; +``` + +```sql+sqlite +select + summary, + hangout_link, + start_time, + end_time +from + googleworkspace_calendar_event +where + calendar_id = 'company-calendar@domain.com' + and start_time >= date('now', 'weekday 0', '-7 days') + and start_time < date('now', 'weekday 0') +order by start_time; +``` + +### List out of office (OOO) events in next 30 days +Discover the upcoming out of office events in the next month. This is useful for planning and coordinating team schedules and resources. + +```sql+postgres +select + summary, + start_time +from + googleworkspace_calendar_event +where + calendar_id = 'company-calendar@domain.com' + and event_type = 'outOfOffice' + and start_time >= current_date + and start_time < current_date + interval '30 days' +order by start_time; +``` + +```sql+sqlite +select + summary, + start_time +from + googleworkspace_calendar_event +where + calendar_id = 'company-calendar@domain.com' + and event_type = 'outOfOffice' + and start_time >= date('now') + and start_time < date('now', '+30 days') +order by start_time; +``` + +### List upcoming Indian holidays in next 30 days +Discover the upcoming holidays in India within the next month. This can be useful for planning activities, scheduling events, or understanding potential business impacts due to national holidays. + +```sql+postgres +select + summary, + start_time, + day +from + googleworkspace_calendar_event +where + calendar_id = 'en.indian#holiday@group.v.calendar.google.com' + and start_time >= current_date + and start_time < current_date + interval '30 days' +order by start_time; +``` + +```sql+sqlite +select + summary, + start_time, + day +from + googleworkspace_calendar_event +where + calendar_id = 'en.indian#holiday@group.v.calendar.google.com' + and start_time >= date('now') + and start_time < date('now', '+30 days') +order by start_time; +``` \ No newline at end of file diff --git a/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_calendar_my_event.md b/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_calendar_my_event.md new file mode 100644 index 00000000..1547d9cf --- /dev/null +++ b/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_calendar_my_event.md @@ -0,0 +1,200 @@ +--- +title: "Steampipe Table: googleworkspace_calendar_my_event - Query Google Workspace Calendar Events using SQL" +description: "Allows users to query Google Workspace Calendar Events, specifically the events of the authenticated user, providing insights into event details and schedules." +--- + +# Table: googleworkspace_calendar_my_event - Query Google Workspace Calendar Events using SQL + +Google Workspace Calendar is a core service within Google Workspace that allows users to schedule events, invite people, and customize their calendars to suit their needs. It provides a centralized way to manage schedules, meetings, and appointments, helping users stay organized and informed about their upcoming events. Google Workspace Calendar helps you stay updated about your schedule and take necessary actions when needed. + +## Table Usage Guide + +The `googleworkspace_calendar_my_event` table provides insights into Google Workspace Calendar Events. As an administrator or a user, explore event-specific details through this table, including event start and end times, attendees, and event status. Utilize it to uncover information about your events, such as those with conflicting schedules, attendees' responses to event invitations, and details about recurring events. + +## Examples + +### Basic info +Gain insights into upcoming events from your Google Workspace Calendar. This query allows you to plan and prioritize by providing a snapshot of the next 10 events, including their summaries and associated hangout links. + +```sql+postgres +select + summary, + hangout_link, + start_time, + end_time +from + googleworkspace_calendar_my_event +order by start_time +limit 10; +``` + +```sql+sqlite +select + summary, + hangout_link, + start_time, + end_time +from + googleworkspace_calendar_my_event +order by start_time +limit 10; +``` + +### List events scheduled for tomorrow +Gain insights into your upcoming events by pinpointing the specific ones scheduled for tomorrow, allowing for effective planning and time management. + +```sql+postgres +select + summary, + hangout_link, + start_time, + end_time +from + googleworkspace_calendar_my_event +where + start_time >= (current_date + interval '1 day') + and start_time < (current_date + interval '2 days') +order by start_time; +``` + +```sql+sqlite +select + summary, + hangout_link, + start_time, + end_time +from + googleworkspace_calendar_my_event +where + start_time >= date('now', '+1 day') + and start_time < date('now', '+2 day') +order by start_time; +``` + +### List events scheduled in next 4 days +Discover the segments that have events scheduled in the coming four days. This is useful for planning and managing your schedule effectively. + +```sql+postgres +select + summary, + hangout_link, + start_time, + end_time +from + googleworkspace_calendar_my_event +where + start_time >= current_date + and start_time <= (current_date + interval '4 days') +order by start_time; +``` + +```sql+sqlite +select + summary, + hangout_link, + start_time, + end_time +from + googleworkspace_calendar_my_event +where + start_time >= date('now') + and start_time <= date('now', '+4 days') +order by start_time; +``` + +### List events scheduled in current month +Explore which events are scheduled for the current month to manage your time and plan accordingly. This allows you to gain insights into your schedule, helping to avoid clashes and ensure efficient time management. + +```sql+postgres +select + summary, + hangout_link, + start_time, + end_time +from + googleworkspace_calendar_my_event +where + start_time >= date_trunc('month', current_date) + and start_time <= date_trunc('month', current_date) + interval '1 month' +order by start_time; +``` + +```sql+sqlite +select + summary, + hangout_link, + start_time, + end_time +from + googleworkspace_calendar_my_event +where + start_time >= date('now','start of month') + and start_time <= date('now','start of month','+1 month') +order by start_time; +``` + +### List events scheduled in current week +Explore the schedule for the current week to understand your upcoming commitments and plan accordingly. This helps in efficiently managing your time by gaining insights into the events lined up for the week. + +```sql+postgres +select + summary, + hangout_link, + start_time, + end_time +from + googleworkspace_calendar_my_event +where + start_time >= date_trunc('week', current_date) + and start_time < (date_trunc('week', current_date) + interval '7 days') +order by start_time; +``` + +```sql+sqlite +select + summary, + hangout_link, + start_time, + end_time +from + googleworkspace_calendar_my_event +where + start_time >= date('now', 'weekday 0', '-7 days') + and start_time < date('now', 'weekday 0') +order by start_time; +``` + +### List upcoming events scheduled on every Tuesday and Thursday +Discover the segments that have upcoming events scheduled on Tuesdays and Thursdays. This is useful for planning and organizing your week ahead with a focus on those specific days. + +```sql+postgres +select + summary, + hangout_link, + start_time, + end_time, + day +from + googleworkspace_calendar_my_event +where + extract(dow from start_time) in (2, 4) + and start_time >= current_date +order by start_time +limit 10; +``` + +```sql+sqlite +select + summary, + hangout_link, + start_time, + end_time, + day +from + googleworkspace_calendar_my_event +where + strftime('%w', start_time) in ('2', '4') + and date(start_time) >= date('now') +order by start_time +limit 10; +``` \ No newline at end of file diff --git a/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_drive.md b/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_drive.md new file mode 100644 index 00000000..01c732cd --- /dev/null +++ b/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_drive.md @@ -0,0 +1,152 @@ +--- +title: "Steampipe Table: googleworkspace_drive - Query Google Workspace Drives using SQL" +description: "Allows users to query Google Workspace Drives, providing detailed information about each drive, including the drive's type, name, theme, and other metadata." +--- + +# Table: googleworkspace_drive - Query Google Workspace Drives using SQL + +Google Workspace Drive is a cloud-based storage solution provided by Google. It allows users to store, share, and synchronize files across devices. Google Workspace Drive supports various file formats and offers features like real-time collaboration, powerful search, and seamless integration with Google Workspace apps. + +## Table Usage Guide + +The `googleworkspace_drive` table provides insights into Drives within Google Workspace. As a system administrator or a DevOps engineer, explore specific details about each drive through this table, including the drive's type, name, theme, and other metadata. Utilize it to understand the distribution and organization of files, monitor the usage of storage space, and manage access control for sensitive files. + +**Important Notes** +- To filter the resource using `name`, or `created_time` you must set `use_domain_admin_access` setting as true** in the where clause, and for that you must have admin access in the domain. See [Shared drive-specific query terms](https://developers.google.com/drive/api/v3/ref-search-terms#drive_properties) for information on `use_domain_admin_access` setting. + +## Examples + +### Basic info +Discover the segments that are hidden within your Google Workspace Drive. This allows you to assess elements within your Drive, such as identifying instances where files or folders have been hidden, to better manage your resources. + +```sql+postgres +select + name, + id, + created_time, + hidden +from + googleworkspace_drive; +``` + +```sql+sqlite +select + name, + id, + created_time, + hidden +from + googleworkspace_drive; +``` + +### List hidden drives +Discover the segments that contain hidden drives within the Google Workspace. This can be beneficial in identifying any potentially unauthorized or suspicious activity. + +```sql+postgres +select + name, + id, + created_time, + hidden +from + googleworkspace_drive +where + hidden; +``` + +```sql+sqlite +select + name, + id, + created_time, + hidden +from + googleworkspace_drive +where + hidden = 1; +``` + +### List drives that allows access to users outside the domain +Explore which Google Workspace drives permit access to users outside of the domain. This is useful for assessing potential security risks and ensuring data is shared appropriately. + +```sql+postgres +select + name, + id, + created_time, + domain_users_only +from + googleworkspace_drive +where + not domain_users_only; +``` + +```sql+sqlite +select + name, + id, + created_time, + domain_users_only +from + googleworkspace_drive +where + domain_users_only = 0; +``` + +### List drives older than 90 days +Determine the areas in which Google Workspace drives have been in use for more than 90 days. This can be useful for identifying potentially outdated or unused resources, contributing to more efficient resource management. + +```sql+postgres +select + name, + id, + created_time, + domain_users_only +from + googleworkspace_drive +where + created_time <= current_date - interval '90 days' + and use_domain_admin_access; +``` + +```sql+sqlite +select + name, + id, + created_time, + domain_users_only +from + googleworkspace_drive +where + created_time <= date('now','-90 day') + and use_domain_admin_access; +``` + +### List drives using the [query filter](https://developers.google.com/drive/api/v3/ref-search-terms#drive_properties) +Explore which Google Workspace drives were created after August 1, 2021, and contain 'steampipe' in their names. This is useful for administrators who want to monitor the creation and naming of drives within their domain. + +```sql+postgres +select + name, + id, + created_time, + domain_users_only +from + googleworkspace_drive +where + query = 'createdTime > ''2021-08-01T07:00:00'' and name contains ''steampipe''' + and use_domain_admin_access; +``` + +```sql+sqlite +select + name, + id, + created_time, + domain_users_only +from + googleworkspace_drive +where + query = 'createdTime > ''2021-08-01T07:00:00'' and name contains ''steampipe''' + and use_domain_admin_access; +``` \ No newline at end of file diff --git a/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_drive_my_file.md b/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_drive_my_file.md new file mode 100644 index 00000000..36612856 --- /dev/null +++ b/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_drive_my_file.md @@ -0,0 +1,130 @@ +--- +title: "Steampipe Table: googleworkspace_drive_my_file - Query Google Workspace Drive Files using SQL" +description: "Allows users to query Google Workspace Drive Files, providing insights into file details, ownership, sharing settings, and more." +--- + +# Table: googleworkspace_drive_my_file - Query Google Workspace Drive Files using SQL + +Google Workspace Drive is a cloud storage service within Google Workspace that allows users to store, sync, and share files. It provides a centralized way to manage files, including documents, spreadsheets, presentations, and more. Google Workspace Drive helps users collaborate on files in real time and access them from any device. + +## Table Usage Guide + +The `googleworkspace_drive_my_file` table provides insights into files within Google Workspace Drive. As a Google Workspace administrator, explore file-specific details through this table, including ownership, sharing settings, and associated metadata. Utilize it to uncover information about files, such as those shared externally, the permissions associated with each file, and the verification of sharing policies. + +## Examples + +### Basic info +Discover the segments that have recently been created within your Google Workspace Drive. This can help you keep track of new additions and manage your files more efficiently. + +```sql+postgres +select + name, + id, + mime_type, + created_time +from + googleworkspace_drive_my_file; +``` + +```sql+sqlite +select + name, + id, + mime_type, + created_time +from + googleworkspace_drive_my_file; +``` + +### List files shared by other users +Gain insights into files shared with you by other users in Google Workspace. This is particularly useful for understanding the scope of shared resources and identifying who has shared files with you. + +```sql+postgres +select + name, + id, + mime_type, + created_time, + owned_by_me, + shared, + sharing_user ->> 'displayName' as sharing_user_name +from + googleworkspace_drive_my_file +where + not owned_by_me + and sharing_user is not null; +``` + +```sql+sqlite +select + name, + id, + mime_type, + created_time, + owned_by_me, + shared, + json_extract(sharing_user, '$.displayName') as sharing_user_name +from + googleworkspace_drive_my_file +where + not owned_by_me + and sharing_user is not null; +``` + +### List image or video files modified after a specific date +Analyze your Google Workspace Drive to pinpoint specific image or video files that have been modified after a certain date. This can be useful to track recent changes or updates to media files in your drive. + +```sql+postgres +select + name, + id, + mime_type, + created_time, + web_view_link +from + googleworkspace_drive_my_file +where + query = 'modifiedTime > "2021-08-15T00:00:00" and (mimeType contains "image/" or mimeType contains "video/")'; +``` + +```sql+sqlite +select + name, + id, + mime_type, + created_time, + web_view_link +from + googleworkspace_drive_my_file +where + strftime('%Y-%m-%dT%H:%M:%S', created_time) > "2021-08-15T00:00:00" and (mime_type like '%image/%' or mime_type like '%video/%'); +``` + +### List files using the [query filter](https://developers.google.com/drive/api/v3/search-files) +Explore which files in your Google Workspace Drive contain the term "Steampipe". This can be particularly useful for quickly locating specific documents or resources related to Steampipe within your workspace. + +```sql+postgres +select + name, + id, + mime_type, + created_time, + web_view_link +from + googleworkspace_drive_my_file +where + query = 'name contains "steampipe"'; +``` + +```sql+sqlite +select + name, + id, + mime_type, + created_time, + web_view_link +from + googleworkspace_drive_my_file +where + query = 'name contains "steampipe"'; +``` \ No newline at end of file diff --git a/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_gmail_draft.md b/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_gmail_draft.md new file mode 100644 index 00000000..d2096cff --- /dev/null +++ b/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_gmail_draft.md @@ -0,0 +1,147 @@ +--- +title: "Steampipe Table: googleworkspace_gmail_draft - Query Google Workspace Gmail Drafts using SQL" +description: "Allows users to query Gmail Drafts in Google Workspace, specifically the metadata and content of draft emails, providing insights into saved but unsent communications." +--- + +# Table: googleworkspace_gmail_draft - Query Google Workspace Gmail Drafts using SQL + +Google Workspace's Gmail service offers a Drafts feature, where users can create, save, and manage draft emails before sending them. This feature provides a space for composing and editing emails, which can be saved for later completion and dispatch. The drafts can contain a variety of information, including recipients, subject lines, and body content. + +## Table Usage Guide + +The `googleworkspace_gmail_draft` table provides insights into draft emails within Google Workspace's Gmail service. As an IT administrator or security analyst, explore draft-specific details through this table, including metadata, message content, and associated user information. Utilize it to uncover information about unsent communications, such as those containing sensitive information, drafts saved by specific users, and the content of these saved but unsent messages. + +**Important Notes** +- You must specify the `user_id` in the `where` or join clause (`where user_id=`, `join googleworkspace_gmail_draft g on g.user_id=`) to query this table. + +## Examples + +### Basic info +Explore which drafts in your Google Workspace Gmail account have a specific user ID. This can help you manage your drafts more effectively by identifying which drafts belong to a specific user, especially useful in large organizations where multiple users may be using the same account. + +```sql+postgres +select + draft_id, + message_id, + message_thread_id, + message_internal_date, + message_size_estimate, + message_snippet +from + googleworkspace_gmail_draft +where + user_id = 'user@domain.com'; +``` + +```sql+sqlite +select + draft_id, + message_id, + message_thread_id, + message_internal_date, + message_size_estimate, + message_snippet +from + googleworkspace_gmail_draft +where + user_id = 'user@domain.com'; +``` + +### List unread draft messages +Discover the segments that contain unread draft messages in your Gmail account. This can be especially useful for managing your email workflow and ensuring important drafts don't get overlooked. + +```sql+postgres +select + draft_id, + message_id, + message_thread_id, + message_internal_date, + message_size_estimate, + message_snippet +from + googleworkspace_gmail_draft +where + user_id = 'user@domain.com' + and query = 'is:unread'; +``` + +```sql+sqlite +select + draft_id, + message_id, + message_thread_id, + message_internal_date, + message_size_estimate, + message_snippet +from + googleworkspace_gmail_draft +where + user_id = 'user@domain.com' + and query = 'is:unread'; +``` + +### List draft messages older than 30 days +Explore which draft messages have been left untouched for over 30 days. This could be useful for clearing out old drafts or identifying potential forgotten tasks. + +```sql+postgres +select + draft_id, + message_id, + message_thread_id, + message_internal_date, + message_size_estimate, + message_snippet +from + googleworkspace_gmail_draft +where + user_id = 'user@domain.com' + and message_internal_date <= (current_date - interval '30' day); +``` + +```sql+sqlite +select + draft_id, + message_id, + message_thread_id, + message_internal_date, + message_size_estimate, + message_snippet +from + googleworkspace_gmail_draft +where + user_id = 'user@domain.com' + and message_internal_date <= date('now','-30 day'); +``` + +### List draft messages without a body +Discover the segments that consist of draft messages without any content. This can be useful for identifying and cleaning up unnecessary drafts, freeing up storage space and keeping your draft folder organized. + +```sql+postgres +select + draft_id, + message_id, + message_thread_id, + message_internal_date, + message_size_estimate, + message_snippet +from + googleworkspace_gmail_draft +where + user_id = 'user@domain.com' + and message_snippet is null; +``` + +```sql+sqlite +select + draft_id, + message_id, + message_thread_id, + message_internal_date, + message_size_estimate, + message_snippet +from + googleworkspace_gmail_draft +where + user_id = 'user@domain.com' + and message_snippet is null; +``` \ No newline at end of file diff --git a/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_gmail_message.md b/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_gmail_message.md new file mode 100644 index 00000000..bee202d6 --- /dev/null +++ b/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_gmail_message.md @@ -0,0 +1,182 @@ +--- +title: "Steampipe Table: googleworkspace_gmail_message - Query Google Workspace Gmail Messages using SQL" +description: "Allows users to query Gmail Messages in Google Workspace, specifically the detailed information about each message, providing insights into message metadata, labels, and thread details." +--- + +# Table: googleworkspace_gmail_message - Query Google Workspace Gmail Messages using SQL + +A Gmail Message is a communication between two or more parties, typically in the form of an email. In Google Workspace, each message is associated with a unique ID and can contain various metadata, such as sender and recipient information, date and time stamps, and labels. Messages can be part of a thread, which groups together related messages for easier navigation and organization. + +## Table Usage Guide + +The `googleworkspace_gmail_message` table provides insights into Gmail Messages within Google Workspace. As a system administrator, explore message-specific details through this table, including metadata, labels, and thread information. Utilize it to uncover information about messages, such as those with specific labels, the relationships between messages and threads, and the verification of sender and recipient details. + +**Important Notes** +- You must specify the `user_id` in the `where` or join clause (`where user_id=`, `join googleworkspace_gmail_my_message g on g.user_id=`) to query this table. + +## Examples + +### Basic info +Explore the basic information of your Gmail messages, such as their ID, thread ID, date, size estimate, and snippet. This is useful to gain insights into your Gmail activity and help manage your inbox effectively. + +```sql+postgres +select + id, + thread_id, + internal_date, + size_estimate, + snippet +from + googleworkspace_gmail_message +where + user_id = 'user@domain.com' +order by internal_date +limit 10; +``` + +```sql+sqlite +select + id, + thread_id, + internal_date, + size_estimate, + snippet +from + googleworkspace_gmail_message +where + user_id = 'user@domain.com' +order by internal_date +limit 10; +``` + +### List unread messages received in last 2 days +Discover recent unread messages in your Gmail account. This query is useful for prioritizing your response to recent and unattended communications. + +```sql+postgres +select + id, + thread_id, + internal_date, + size_estimate, + snippet +from + googleworkspace_gmail_message +where + user_id = 'user@domain.com' + and query = 'is:unread newer_than:2d' +order by internal_date; +``` + +```sql+sqlite +select + id, + thread_id, + internal_date, + size_estimate, + snippet +from + googleworkspace_gmail_message +where + user_id = 'user@domain.com' + and query = 'is:unread newer_than:2d' +order by internal_date; +``` + +### List messages from a specific user +Explore messages from a specific user in your Google Workspace Gmail account to gain insights into communication trends. This is particularly useful for understanding the frequency and content of interactions with specific individuals. + +```sql+postgres +select + id, + thread_id, + internal_date, + size_estimate, + snippet +from + googleworkspace_gmail_message +where + user_id = 'user@domain.com' + and query = 'from:someuser@example.com' +order by internal_date; +``` + +```sql+sqlite +select + id, + thread_id, + internal_date, + size_estimate, + snippet +from + googleworkspace_gmail_message +where + user_id = 'user@domain.com' + and query = 'from:someuser@example.com' +order by internal_date; +``` + +### List draft messages +Explore your draft messages in Gmail to gain insights into their content and size, and to determine their chronological order. This can be useful for managing and organizing your drafts effectively. + +```sql+postgres +select + id, + thread_id, + internal_date, + size_estimate, + snippet +from + googleworkspace_gmail_message +where + user_id = 'user@domain.com' + and query = 'in:draft' +order by internal_date; +``` + +```sql+sqlite +select + id, + thread_id, + internal_date, + size_estimate, + snippet +from + googleworkspace_gmail_message +where + user_id = 'user@domain.com' + and query = 'in:draft' +order by internal_date; +``` + +### List chat messages +Explore your Google Workspace Gmail chat messages to gain insights into the content and timing of your conversations. This could be useful in understanding communication patterns or tracking specific discussions. + +```sql+postgres +select + id, + thread_id, + internal_date, + size_estimate, + snippet +from + googleworkspace_gmail_message +where + user_id = 'user@domain.com' + and query = 'in:chats' +order by internal_date; +``` + +```sql+sqlite +select + id, + thread_id, + internal_date, + size_estimate, + snippet +from + googleworkspace_gmail_message +where + user_id = 'user@domain.com' + and query = 'in:chats' +order by internal_date; +``` \ No newline at end of file diff --git a/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_gmail_my_draft.md b/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_gmail_my_draft.md new file mode 100644 index 00000000..4e6132de --- /dev/null +++ b/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_gmail_my_draft.md @@ -0,0 +1,134 @@ +--- +title: "Steampipe Table: googleworkspace_gmail_my_draft - Query Google Workspace Gmail Drafts using SQL" +description: "Allows users to query Gmail Drafts in Google Workspace, specifically the draft messages and their details, providing insights into the content, status, and metadata of drafts." +--- + +# Table: googleworkspace_gmail_my_draft - Query Google Workspace Gmail Drafts using SQL + +Gmail Drafts in Google Workspace is a feature that allows users to save and manage draft messages before they are sent. These drafts include not only the content of the potential email but also metadata such as the draft's ID, message ID, and thread ID. Gmail Drafts serves as a useful tool for managing email communications and tracking unsent messages within a Google Workspace environment. + +## Table Usage Guide + +The `googleworkspace_gmail_my_draft` table provides insights into draft messages within Google Workspace's Gmail. As an IT administrator, explore draft-specific details through this table, including content, status, and associated metadata. Utilize it to uncover information about drafts, such as those that have been left unsent or abandoned, and the details of these drafts, to better manage email communications within your organization. + +## Examples + +### Basic info +Explore which drafts in your Google Workspace Gmail account have a large estimated size. This can help manage your storage space and identify drafts that may be too large to send. + +```sql+postgres +select + draft_id, + message_id, + message_thread_id, + message_internal_date, + message_size_estimate, + message_snippet +from + googleworkspace_gmail_my_draft; +``` + +```sql+sqlite +select + draft_id, + message_id, + message_thread_id, + message_internal_date, + message_size_estimate, + message_snippet +from + googleworkspace_gmail_my_draft; +``` + +### List unread draft messages +Explore which draft messages are still unread. This can help in prioritizing responses and ensuring important communications are not missed. + +```sql+postgres +select + draft_id, + message_id, + message_thread_id, + message_internal_date, + message_size_estimate, + message_snippet +from + googleworkspace_gmail_my_draft +where + query = 'is:unread'; +``` + +```sql+sqlite +select + draft_id, + message_id, + message_thread_id, + message_internal_date, + message_size_estimate, + message_snippet +from + googleworkspace_gmail_my_draft +where + query = 'is:unread'; +``` + +### List draft messages older than 30 days +Explore which draft messages have been left untouched for over a month. This query is useful in identifying stale drafts that might need attention or deletion. + +```sql+postgres +select + draft_id, + message_id, + message_thread_id, + message_internal_date, + message_size_estimate, + message_snippet +from + googleworkspace_gmail_my_draft +where + message_internal_date <= (current_date - interval '30' day); +``` + +```sql+sqlite +select + draft_id, + message_id, + message_thread_id, + message_internal_date, + message_size_estimate, + message_snippet +from + googleworkspace_gmail_my_draft +where + message_internal_date <= date('now','-30 day'); +``` + +### List draft messages without a body +Uncover the details of draft emails that lack content. This query is particularly useful when you want to clean up your drafts folder by identifying and removing empty draft messages. + +```sql+postgres +select + draft_id, + message_id, + message_thread_id, + message_internal_date, + message_size_estimate, + message_snippet +from + googleworkspace_gmail_my_draft +where + message_snippet is null; +``` + +```sql+sqlite +select + draft_id, + message_id, + message_thread_id, + message_internal_date, + message_size_estimate, + message_snippet +from + googleworkspace_gmail_my_draft +where + message_snippet is null; +``` \ No newline at end of file diff --git a/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_gmail_my_message.md b/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_gmail_my_message.md new file mode 100644 index 00000000..b4288b5a --- /dev/null +++ b/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_gmail_my_message.md @@ -0,0 +1,167 @@ +--- +title: "Steampipe Table: googleworkspace_gmail_my_message - Query Google Workspace Gmail Messages using SQL" +description: "Allows users to query Gmail Messages in Google Workspace, specifically the details of the user's messages, providing insights into email communication and potential anomalies." +--- + +# Table: googleworkspace_gmail_my_message - Query Google Workspace Gmail Messages using SQL + +Gmail is a service within Google Workspace that provides a robust and secure platform for sending, receiving, and storing email. It offers an intuitive interface for users to manage their emails, including features such as spam filtering, conversation view, and powerful search. Gmail is designed to be accessed on any device, providing flexibility and continuity for users on the go. + +## Table Usage Guide + +The `googleworkspace_gmail_my_message` table provides insights into Gmail Messages within Google Workspace. As a system administrator, explore message-specific details through this table, including the sender, recipient, subject, and timestamp. Utilize it to uncover information about messages, such as those marked as spam, the communication patterns, and the verification of message headers. + +## Examples + +### Basic info +Explore your recent Gmail messages to gain insights into their content and size. This aids in managing your inbox by identifying large or outdated threads. + +```sql+postgres +select + id, + thread_id, + internal_date, + size_estimate, + snippet +from + googleworkspace_gmail_my_message +order by internal_date +limit 10; +``` + +```sql+sqlite +select + id, + thread_id, + internal_date, + size_estimate, + snippet +from + googleworkspace_gmail_my_message +order by internal_date +limit 10; +``` + +### List unread messages received in last 2 days +Explore which unread messages have been received in the last two days. This helps to prioritize the most recent and potentially urgent communications that require your attention. + +```sql+postgres +select + id, + thread_id, + internal_date, + size_estimate, + snippet +from + googleworkspace_gmail_my_message +where + query = 'is:unread newer_than:2d' +order by internal_date; +``` + +```sql+sqlite +select + id, + thread_id, + internal_date, + size_estimate, + snippet +from + googleworkspace_gmail_my_message +where + query = 'is:unread newer_than:2d' +order by internal_date; +``` + +### List messages from a specific user +Explore the communication history of a specific user to understand the context and frequency of their interactions. This is particularly useful in situations where you need to track the activity of a particular individual for audit or investigation purposes. + +```sql+postgres +select + id, + thread_id, + internal_date, + size_estimate, + snippet +from + googleworkspace_gmail_my_message +where + query = 'from:someuser@example.com' +order by internal_date; +``` + +```sql+sqlite +select + id, + thread_id, + internal_date, + size_estimate, + snippet +from + googleworkspace_gmail_my_message +where + query = 'from:someuser@example.com' +order by internal_date; +``` + +### List draft messages +Explore your draft messages in Gmail to understand their content and size. This can help in managing your drafts more effectively by identifying large drafts or older drafts that may need attention. + +```sql+postgres +select + id, + thread_id, + internal_date, + size_estimate, + snippet +from + googleworkspace_gmail_my_message +where + query = 'in:draft' +order by internal_date; +``` + +```sql+sqlite +select + id, + thread_id, + internal_date, + size_estimate, + snippet +from + googleworkspace_gmail_my_message +where + query = 'in:draft' +order by internal_date; +``` + +### List chat messages +Explore your chat history to gain insights into the frequency and content of your interactions. This can be useful for assessing communication patterns and identifying key conversations. + +```sql+postgres +select + id, + thread_id, + internal_date, + size_estimate, + snippet +from + googleworkspace_gmail_my_message +where + query = 'in:chats' +order by internal_date; +``` + +```sql+sqlite +select + id, + thread_id, + internal_date, + size_estimate, + snippet +from + googleworkspace_gmail_my_message +where + query = 'in:chats' +order by internal_date; +``` \ No newline at end of file diff --git a/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_gmail_my_settings.md b/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_gmail_my_settings.md new file mode 100644 index 00000000..8bbbaff5 --- /dev/null +++ b/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_gmail_my_settings.md @@ -0,0 +1,138 @@ +--- +title: "Steampipe Table: googleworkspace_gmail_my_settings - Query Google Workspace Gmail Settings using SQL" +description: "Allows users to query Gmail Settings in Google Workspace, specifically the user's email settings including filters, forwarding rules, IMAP and POP settings, and send-as aliases." +--- + +# Table: googleworkspace_gmail_my_settings - Query Google Workspace Gmail Settings using SQL + +Gmail Settings in Google Workspace is a feature that allows users to customize their Gmail experience according to their preferences. It includes options to manage filters, forwarding rules, IMAP and POP settings, and send-as aliases. These settings help users to manage their email communication effectively and efficiently. + +## Table Usage Guide + +The `googleworkspace_gmail_my_settings` table provides insights into the Gmail Settings in Google Workspace. As a system administrator or a user, explore your email settings through this table, including filters, forwarding rules, IMAP and POP settings, and send-as aliases. Utilize it to uncover information about your Gmail settings, such as those related to email forwarding, the filters applied to incoming emails, and the configuration of IMAP and POP settings. + +**Important Notes** +- To list delegated accounts, you must authenticate using a service account client that has been delegated domain-wide authority. + +## Examples + +### Basic info +Explore the language settings and delegation details associated with your Google Workspace Gmail account. This can be useful for understanding user preferences and managing access rights. + +```sql+postgres +select + user_email, + display_language, + delegates +from + googleworkspace_gmail_my_settings; +``` + +```sql+sqlite +select + user_email, + display_language, + delegates +from + googleworkspace_gmail_my_settings; +``` + +### List users with delegated access to their mailbox +Explore which users have delegated access to their mailbox in Google Workspace. This can be useful for assessing security and access control within your organization. + +```sql+postgres +select + user_email, + display_language, + delegates +from + googleworkspace_gmail_my_settings +where + delegates is not null; +``` + +```sql+sqlite +select + user_email, + display_language, + delegates +from + googleworkspace_gmail_my_settings +where + delegates is not null; +``` + +### List users with IMAP access enabled +Explore which users have IMAP access enabled in their Gmail settings. This could be useful for administrators looking to manage or restrict certain types of email access. + +```sql+postgres +select + user_email, + display_language, + (imap ->> 'enabled')::boolean as imap_enabled +from + googleworkspace_gmail_my_settings +where + (imap ->> 'enabled')::boolean; +``` + +```sql+sqlite +select + user_email, + display_language, + json_extract(imap, '$.enabled') as imap_enabled +from + googleworkspace_gmail_my_settings +where + json_extract(imap, '$.enabled'); +``` + +### List users with POP access enabled +Identify instances where users have enabled POP access in their Gmail settings. This is useful in understanding the email access preferences within your organization. + +```sql+postgres +select + user_email, + display_language, + pop ->> 'accessWindow' as pop_access_window +from + googleworkspace_gmail_my_settings +where + pop ->> 'accessWindow' = 'enabled'; +``` + +```sql+sqlite +select + user_email, + display_language, + json_extract(pop, '$.accessWindow') as pop_access_window +from + googleworkspace_gmail_my_settings +where + json_extract(pop, '$.accessWindow') = 'enabled'; +``` + +### List users with automatic forwarding enabled +Determine the areas in which users have enabled automatic forwarding in their email settings. This is useful for understanding the flow of information within your organization and ensuring compliance with communication policies. + +```sql+postgres +select + user_email, + display_language, + (auto_forwarding ->> 'enabled')::boolean as auto_forwarding_enabled +from + googleworkspace_gmail_my_settings +where + (auto_forwarding ->> 'enabled')::boolean; +``` + +```sql+sqlite +select + user_email, + display_language, + json_extract(auto_forwarding, '$.enabled') as auto_forwarding_enabled +from + googleworkspace_gmail_my_settings +where + json_extract(auto_forwarding, '$.enabled'); +``` \ No newline at end of file diff --git a/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_gmail_settings.md b/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_gmail_settings.md new file mode 100644 index 00000000..97868d01 --- /dev/null +++ b/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_gmail_settings.md @@ -0,0 +1,151 @@ +--- +title: "Steampipe Table: googleworkspace_gmail_settings - Query Google Workspace Gmail Settings using SQL" +description: "Allows users to query Gmail Settings in Google Workspace, providing insights into individual user settings and preferences within Gmail." +--- + +# Table: googleworkspace_gmail_settings - Query Google Workspace Gmail Settings using SQL + +Google Workspace's Gmail service is a powerful email platform used by organizations globally. Its settings include various user preferences and configurations that govern the behavior of the Gmail interface for individual users. These settings encompass aspects such as display language, page size, keyboard shortcuts, and email forwarding rules. + +## Table Usage Guide + +The `googleworkspace_gmail_settings` table provides insights into individual user settings within Google Workspace's Gmail service. As a system administrator or IT professional, you can use this table to explore and manage user-specific settings and preferences in Gmail. This includes information on display language, email forwarding rules, keyboard shortcuts, and more, enabling efficient management and troubleshooting of user issues. + +**Important Notes** +- You must specify the `user_email` in the `where` or join clause (`where user_email=`, `join googleworkspace_gmail_settings g on g.user_email=`) to query this table. +- To list delegated accounts, you must authenticate using a service account client that has been delegated domain-wide authority. + +## Examples + +### Basic info +Explore the language settings and delegates associated with a specific user's Gmail account. This can help in understanding the user's preferred language and who has access to their account. + +```sql+postgres +select + user_email, + display_language, + delegates +from + googleworkspace_gmail_settings +where + user_email = 'user@domain.com'; +``` + +```sql+sqlite +select + user_email, + display_language, + delegates +from + googleworkspace_gmail_settings +where + user_email = 'user@domain.com'; +``` + +### List users with delegated access to their mailbox +Explore which users have granted others access to their mailbox, a useful feature for shared email accounts or teams managing a common inbox. + +```sql+postgres +select + user_email, + display_language, + delegates +from + googleworkspace_gmail_settings +where + user_email = 'user@domain.com' + and delegates is not null; +``` + +```sql+sqlite +select + user_email, + display_language, + delegates +from + googleworkspace_gmail_settings +where + user_email = 'user@domain.com' + and delegates is not null; +``` + +### List users with IMAP access enabled +Analyze the settings to understand which users have enabled IMAP access in their Google Workspace Gmail settings. This can help in auditing user access and ensuring compliance with company email policies. + +```sql+postgres +select + user_email, + display_language, + (imap ->> 'enabled')::boolean as imap_enabled +from + googleworkspace_gmail_settings +where + user_email = 'user@domain.com' + and (imap ->> 'enabled')::boolean; +``` + +```sql+sqlite +select + user_email, + display_language, + json_extract(imap, '$.enabled') as imap_enabled +from + googleworkspace_gmail_settings +where + user_email = 'user@domain.com' + and json_extract(imap, '$.enabled'); +``` + +### List users with POP access enabled +Explore which users have POP access enabled in their email settings. This is useful for identifying potential security risks or ensuring compliance with company policies regarding email access methods. + +```sql+postgres +select + user_email, + display_language, + pop ->> 'accessWindow' as pop_access_window +from + googleworkspace_gmail_settings +where + user_email = 'user@domain.com' + and pop ->> 'accessWindow' = 'enabled'; +``` + +```sql+sqlite +select + user_email, + display_language, + json_extract(pop, '$.accessWindow') as pop_access_window +from + googleworkspace_gmail_settings +where + user_email = 'user@domain.com' + and json_extract(pop, '$.accessWindow') = 'enabled'; +``` + +### List users with automatic forwarding enabled +Explore which users have automatic forwarding enabled in their email settings. This can be useful in maintaining data privacy and reducing the risk of sensitive information being inadvertently shared. + +```sql+postgres +select + user_email, + display_language, + (auto_forwarding ->> 'enabled')::boolean as auto_forwarding_enabled +from + googleworkspace_gmail_settings +where + user_email = 'user@domain.com' + and (auto_forwarding ->> 'enabled')::boolean; +``` + +```sql+sqlite +select + user_email, + display_language, + json_extract(auto_forwarding, '$.enabled') as auto_forwarding_enabled +from + googleworkspace_gmail_settings +where + user_email = 'user@domain.com' + and json_extract(auto_forwarding, '$.enabled'); +``` \ No newline at end of file diff --git a/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_people_contact.md b/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_people_contact.md new file mode 100644 index 00000000..d56a74d0 --- /dev/null +++ b/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_people_contact.md @@ -0,0 +1,91 @@ +--- +title: "Steampipe Table: googleworkspace_people_contact - Query Google Workspace Contacts using SQL" +description: "Allows users to query Contacts in Google Workspace, specifically the contact details, providing insights into personal and professional information associated with Google Workspace users." +--- + +# Table: googleworkspace_people_contact - Query Google Workspace Contacts using SQL + +Google Workspace Contacts is a resource within Google Workspace that allows users to save and organize contact information. It provides a centralized way to manage contact details for various Google Workspace users, including names, email addresses, phone numbers, and more. Google Workspace Contacts helps users stay connected and maintain a comprehensive directory of contacts within their organization. + +## Table Usage Guide + +The `googleworkspace_people_contact` table provides insights into contact details within Google Workspace. As a system administrator, explore contact-specific details through this table, including names, email addresses, phone numbers, and associated metadata. Utilize it to uncover information about contacts, such as their professional affiliations, communication details, and the verification of associated metadata. + +## Examples + +### Basic info +Explore the basic information of your Google Workspace contacts, such as their names and primary email addresses. This can help you assess and understand your contact organization structure better. + +```sql+postgres +select + resource_name, + display_name, + given_name, + primary_email_address, + jsonb_pretty(organizations) +from + googleworkspace_people_contact; +``` + +```sql+sqlite +select + resource_name, + display_name, + given_name, + primary_email_address, + organizations +from + googleworkspace_people_contact; +``` + +### List contacts by contact group +Explore which contacts belong to specific groups in your Google Workspace. This can be useful for managing communication within teams or identifying groups for targeted outreach. + +```sql+postgres +select + cg.name as contact_group_name, + c.given_name as member_name, + c.primary_email_address as member_primary_email +from + googleworkspace_people_contact as c, + googleworkspace_people_contact_group as cg +where + cg.member_resource_names ?| array[c.resource_name]; +``` + +```sql+sqlite +Error: SQLite does not support array operations and the '?' operator used in PostgreSQL. +``` + +### List contacts belogning to the same organization +Explore which contacts in your Google Workspace belong to the same organization, allowing you to better categorize and manage your professional networks. This is particularly useful for identifying all contacts associated with a specific business entity, such as 'Turbot'. + +```sql+postgres +select + display_name, + primary_email_address, + org ->> 'name' as organization_name, + org ->> 'department' as department, + org ->> 'title' as job_title +from + googleworkspace_people_contact, + jsonb_array_elements(organizations) as org +where + org -> 'metadata' ->> 'primary' = 'true' + and org ->> 'name' = 'Turbot'; +``` + +```sql+sqlite +select + display_name, + primary_email_address, + json_extract(org.value, '$.name') as organization_name, + json_extract(org.value, '$.department') as department, + json_extract(org.value, '$.title') as job_title +from + googleworkspace_people_contact, + json_each(organizations) as org +where + json_extract(org.value, '$.metadata.primary') = 'true' + and json_extract(org.value, '$.name') = 'Turbot'; +``` \ No newline at end of file diff --git a/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_people_contact_group.md b/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_people_contact_group.md new file mode 100644 index 00000000..35f0f13b --- /dev/null +++ b/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_people_contact_group.md @@ -0,0 +1,116 @@ +--- +title: "Steampipe Table: googleworkspace_people_contact_group - Query Google Workspace People Contact Groups using SQL" +description: "Allows users to query People Contact Groups in Google Workspace, providing insights into contact group details and metadata." +--- + +# Table: googleworkspace_people_contact_group - Query Google Workspace People Contact Groups using SQL + +Google Workspace People Contact Groups is a feature within Google Workspace that allows users to create and manage groups of contacts. It offers a centralized way to organize and manage contacts for various Google Workspace applications, including Gmail, Google Meet, and more. People Contact Groups help users efficiently manage communications and collaborations with groups of people. + +## Table Usage Guide + +The `googleworkspace_people_contact_group` table provides insights into People Contact Groups within Google Workspace. As an IT administrator or a Google Workspace user, you can explore group-specific details through this table, including group metadata, member count, and member resource names. Use it to manage and organize your Google Workspace contacts more efficiently, such as identifying large groups, finding groups without members, and understanding the structure of your contact groups. + +## Examples + +### Basic info +Explore the various contact groups in your Google Workspace, including their names and types, to better manage your organization's communication and collaboration. This can be particularly useful in large organizations where understanding group structures is key to efficient operations. + +```sql+postgres +select + resource_name, + name, + formatted_name, + group_type +from + googleworkspace_people_contact_group; +``` + +```sql+sqlite +select + resource_name, + name, + formatted_name, + group_type +from + googleworkspace_people_contact_group; +``` + +### List deleted contact groups +Discover the segments that have been removed from your Google Workspace contact groups. This can be useful to track changes and manage your contacts more effectively. + +```sql+postgres +select + resource_name, + name, + formatted_name, + deleted +from + googleworkspace_people_contact_group +where + deleted; +``` + +```sql+sqlite +select + resource_name, + name, + formatted_name, + deleted +from + googleworkspace_people_contact_group +where + deleted = 1; +``` + +### List members in each contact group +Explore which members belong to each contact group in your Google Workspace, allowing you to better manage communication and collaboration within your organization. This query is particularly useful for gaining insights into group composition and identifying any necessary changes to group membership. + +```sql+postgres +select + cg.name as contact_group_name, + c.given_name as member_name, + c.primary_email_address as member_primary_email +from + googleworkspace_people_contact_group as cg, + jsonb_array_elements_text(member_resource_names) as m_name, + googleworkspace_people_contact as c +where + c.resource_name = m_name; +``` + +```sql+sqlite +select + cg.name as contact_group_name, + c.given_name as member_name, + c.primary_email_address as member_primary_email +from + googleworkspace_people_contact_group as cg, + json_each(cg.member_resource_names) as m_name, + googleworkspace_people_contact as c +where + c.resource_name = m_name.value; +``` + +### Get member count for each contact group +Discover the segments that have varying membership within your contact groups. This query allows you to analyze the size of each group, helping you to better manage your resources and communications. + +```sql+postgres +select + resource_name, + name, + formatted_name, + member_count +from + googleworkspace_people_contact_group; +``` + +```sql+sqlite +select + resource_name, + name, + formatted_name, + member_count +from + googleworkspace_people_contact_group; +``` \ No newline at end of file diff --git a/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_people_directory_people.md b/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_people_directory_people.md new file mode 100644 index 00000000..ed3324b7 --- /dev/null +++ b/steampipe-plugin-googleworkspace/docs/tables/googleworkspace_people_directory_people.md @@ -0,0 +1,49 @@ +--- +title: "Steampipe Table: googleworkspace_people_directory_people - Query Google Workspace Directory People using SQL" +description: "Allows users to query Directory People in Google Workspace, specifically the list of people in the directory, providing insights into user profiles and their metadata." +--- + +# Table: googleworkspace_people_directory_people - Query Google Workspace Directory People using SQL + +Google Workspace Directory People is a resource within Google Workspace that allows you to manage and access user profiles in your organization's directory. It provides a centralized way to view and manage information about people in your organization, including their email addresses, phone numbers, and other profile details. Google Workspace Directory People helps you stay informed about the users in your organization and take appropriate actions when necessary. + +## Table Usage Guide + +The `googleworkspace_people_directory_people` table provides insights into user profiles within Google Workspace Directory People. As an IT administrator, explore user-specific details through this table, including email addresses, phone numbers, and other profile details. Utilize it to uncover information about users, such as their roles, the groups they belong to, and their profile's metadata. + +## Examples + +### Basic info +This query is useful for obtaining key information about individuals in a Google Workspace directory, such as their display name, primary email address, job title, and primary contact number. It's a practical tool for HR teams or managers who need to quickly access or compile this information for communication or organizational purposes. + +```sql+postgres +select + display_name, + primary_email_address, + case + when org -> 'metadata' ->> 'primary' = 'true' then org ->> 'title' + end as job_title, + case + when ph -> 'metadata' ->> 'primary' = 'true' then ph ->> 'value' + end as primary_contact +from + googleworkspace_people_directory_people + left join jsonb_array_elements(organizations) as org on true + left join jsonb_array_elements(phone_numbers) as ph on true; +``` + +```sql+sqlite +select + display_name, + primary_email_address, + case + when json_extract(org.value, '$.metadata.primary') = 'true' then json_extract(org.value, '$.title') + end as job_title, + case + when json_extract(ph.value, '$.metadata.primary') = 'true' then json_extract(ph.value, '$.value') + end as primary_contact +from + googleworkspace_people_directory_people, + json_each(organizations) as org, + json_each(phone_numbers) as ph; +``` \ No newline at end of file diff --git a/steampipe-plugin-googleworkspace/go.mod b/steampipe-plugin-googleworkspace/go.mod new file mode 100644 index 00000000..74a504fd --- /dev/null +++ b/steampipe-plugin-googleworkspace/go.mod @@ -0,0 +1,113 @@ +module github.com/turbot/steampipe-plugin-googleworkspace + +go 1.22.4 + +toolchain go1.22.6 + +require ( + github.com/iancoleman/strcase v0.3.0 + github.com/mitchellh/go-homedir v1.1.0 + github.com/turbot/go-kit v0.10.0-rc.0 + github.com/turbot/steampipe-plugin-sdk/v5 v5.10.4 + golang.org/x/oauth2 v0.17.0 + google.golang.org/api v0.162.0 +) + +require ( + cloud.google.com/go v0.112.0 // indirect + cloud.google.com/go/compute v1.24.0 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/iam v1.1.6 // indirect + cloud.google.com/go/storage v1.36.0 // indirect + github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect + github.com/agext/levenshtein v1.2.2 // indirect + github.com/allegro/bigcache/v3 v3.1.0 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/aws/aws-sdk-go v1.44.122 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect + github.com/btubbs/datetime v0.1.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect + github.com/dgraph-io/ristretto v0.1.1 // indirect + github.com/dustin/go-humanize v1.0.0 // indirect + github.com/eko/gocache/lib/v4 v4.1.5 // indirect + github.com/eko/gocache/store/bigcache/v4 v4.2.1 // indirect + github.com/eko/gocache/store/ristretto/v4 v4.2.1 // indirect + github.com/fatih/color v1.15.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/gertd/go-pluralize v0.2.1 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/glog v1.2.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/mock v1.6.0 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/s2a-go v0.1.7 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect + github.com/googleapis/gax-go/v2 v2.12.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-getter v1.7.5 // indirect + github.com/hashicorp/go-hclog v1.6.2 // indirect + github.com/hashicorp/go-plugin v1.6.0 // indirect + github.com/hashicorp/go-safetemp v1.0.0 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/hcl/v2 v2.20.1 // indirect + github.com/hashicorp/yamux v0.1.1 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/klauspost/compress v1.15.11 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/mitchellh/go-wordwrap v1.0.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/oklog/run v1.0.0 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_golang v1.14.0 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.37.0 // indirect + github.com/prometheus/procfs v0.8.0 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/sethvargo/go-retry v0.2.4 // indirect + github.com/stevenle/topsort v0.2.0 // indirect + github.com/tkrajina/go-reflector v0.5.6 // indirect + github.com/ulikunitz/xz v0.5.10 // indirect + github.com/zclconf/go-cty v1.14.4 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect + go.opentelemetry.io/otel v1.26.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.26.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.26.0 // indirect + go.opentelemetry.io/otel/sdk v1.26.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.26.0 // indirect + go.opentelemetry.io/otel/trace v1.26.0 // indirect + go.opentelemetry.io/proto/otlp v1.2.0 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect + golang.org/x/mod v0.8.0 // indirect + golang.org/x/net v0.23.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.19.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect + golang.org/x/tools v0.6.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect + google.golang.org/grpc v1.63.2 // indirect + google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/steampipe-plugin-googleworkspace/go.sum b/steampipe-plugin-googleworkspace/go.sum new file mode 100644 index 00000000..6b378000 --- /dev/null +++ b/steampipe-plugin-googleworkspace/go.sum @@ -0,0 +1,1166 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= +cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= +cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= +cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM= +cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4= +cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= +cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= +cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= +cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= +cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= +cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= +cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= +cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= +cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= +cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= +cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= +cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= +cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= +cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= +cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= +cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= +cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= +cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= +cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= +cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= +cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= +cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= +cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= +cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= +cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= +cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= +cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= +cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= +cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= +cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= +cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= +cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= +cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= +cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= +cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= +cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= +cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= +cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= +cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= +cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= +cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= +cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= +cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= +cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= +cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= +cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= +cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= +cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= +cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= +cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= +cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= +cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= +cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= +cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= +cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= +cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= +cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= +cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= +cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= +cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= +cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= +cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= +cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= +cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= +cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= +cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= +cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= +cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= +cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= +cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= +cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= +cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= +cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= +cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= +cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= +cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= +cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= +cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= +cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= +cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= +cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= +cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= +cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= +cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= +cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= +cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= +cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= +cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= +cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= +cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= +cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= +cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= +cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= +cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= +cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= +cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= +cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= +cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= +cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= +cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= +cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= +cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= +cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= +cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= +cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= +cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= +cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= +cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= +cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= +cloud.google.com/go/storage v1.36.0 h1:P0mOkAcaJxhCTvAkMhxMfrTKiNcub4YmmPBtlhAyTr8= +cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= +cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= +cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= +cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= +cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= +cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= +cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= +cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= +cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= +cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= +cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= +cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= +github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= +github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/allegro/bigcache/v3 v3.1.0 h1:H2Vp8VOvxcrB91o86fUSVJFqeuz8kpyyB02eH3bSzwk= +github.com/allegro/bigcache/v3 v3.1.0/go.mod h1:aPyh7jEvrog9zAwx5N7+JUQX5dZTSGpxF1LAR4dr35I= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= +github.com/aws/aws-sdk-go v1.44.122 h1:p6mw01WBaNpbdP2xrisz5tIkcNwzj/HysobNoaAHjgo= +github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= +github.com/btubbs/datetime v0.1.1 h1:KuV+F9tyq/hEnezmKZNGk8dzqMVsId6EpFVrQCfA3To= +github.com/btubbs/datetime v0.1.1/go.mod h1:n2BZ/2ltnRzNiz27aE3wUb2onNttQdC+WFxAoks5jJM= +github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= +github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ= +github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= +github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= +github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eko/gocache/lib/v4 v4.1.5 h1:CeMQmdIzwBKKLRjk3FCDXzNFsQTyqJ01JLI7Ib0C9r8= +github.com/eko/gocache/lib/v4 v4.1.5/go.mod h1:XaNfCwW8KYW1bRZ/KoHA1TugnnkMz0/gT51NDIu7LSY= +github.com/eko/gocache/store/bigcache/v4 v4.2.1 h1:xf9R5HZqmrfT4+NzlJPQJQUWftfWW06FHbjz4IEjE08= +github.com/eko/gocache/store/bigcache/v4 v4.2.1/go.mod h1:Q9+hxUE+XUVGSRGP1tqW8sPHcZ50PfyBVh9VKh0OjrA= +github.com/eko/gocache/store/ristretto/v4 v4.2.1 h1:xB5E1LP1gh8yUV1G3KVRSL4T0OTnxp4OixuTljn2848= +github.com/eko/gocache/store/ristretto/v4 v4.2.1/go.mod h1:KyshDyWQqfSVrg2rH06fFQZTj6vG2fxlY7oAW9oxNHY= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= +github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/gertd/go-pluralize v0.2.1 h1:M3uASbVjMnTsPb0PNqg+E/24Vwigyo/tvyMTtAlLgiA= +github.com/gertd/go-pluralize v0.2.1/go.mod h1:rbYaKDbsXxmRfr8uygAEKhOWsjyrrqrkHVpZvoOp8zk= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= +github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= +github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= +github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= +github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-getter v1.7.5 h1:dT58k9hQ/vbxNMwoI5+xFYAJuv6152UNvdHokfI5wE4= +github.com/hashicorp/go-getter v1.7.5/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= +github.com/hashicorp/go-hclog v1.6.2 h1:NOtoftovWkDheyUM/8JW3QMiXyxJK3uHRK7wV04nD2I= +github.com/hashicorp/go-hclog v1.6.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A= +github.com/hashicorp/go-plugin v1.6.0/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/8pwz68aMkCI= +github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= +github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl/v2 v2.20.1 h1:M6hgdyz7HYt1UN9e61j+qKJBqR3orTWbI1HKBJEdxtc= +github.com/hashicorp/hcl/v2 v2.20.1/go.mod h1:TZDqQ4kNKCbh1iJp99FdPiUaVDDUPivbqxZulxDYqL4= +github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= +github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= +github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c= +github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= +github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= +github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/sethvargo/go-retry v0.2.4 h1:T+jHEQy/zKJf5s95UkguisicE0zuF9y7+/vgz08Ocec= +github.com/sethvargo/go-retry v0.2.4/go.mod h1:1afjQuvh7s4gflMObvjLPaWgluLLyhA1wmVZ6KLpICw= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/stevenle/topsort v0.2.0 h1:LLWgtp34HPX6/RBDRS0kElVxGOTzGBLI1lSAa5Lb46k= +github.com/stevenle/topsort v0.2.0/go.mod h1:ck2WG2/ZrOr6dLApQ/5Xrqy5wv3T0qhKYWE7r9tkibc= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQSepKdE= +github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= +github.com/turbot/go-kit v0.10.0-rc.0 h1:kd+jp2ibbIV33Hc8SsMAN410Dl9Pz6SJ40axbKUlSoA= +github.com/turbot/go-kit v0.10.0-rc.0/go.mod h1:fFQqR59I5z5JeeBLfK1PjSifn4Oprs3NiQx0CxeSJxs= +github.com/turbot/steampipe-plugin-sdk/v5 v5.10.4 h1:h2Ye0ksL6KN6w2wh1O3mgKthLnyl4QPsTpUj47bRbvc= +github.com/turbot/steampipe-plugin-sdk/v5 v5.10.4/go.mod h1:FzW+0aq4x1PoCkklCRx43dMrhxk7SYeWVHUb3pNFtFc= +github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= +github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8= +github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY34Wul7O/MSKey3txpPYyCqVO5ZyceuQJEI= +github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= +go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= +go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.26.0 h1:+hm+I+KigBy3M24/h1p/NHkUx/evbLH0PNcjpMyCHc4= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.26.0/go.mod h1:NjC8142mLvvNT6biDpaMjyz78kyEHIwAJlSX0N9P5KI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM= +go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30= +go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4= +go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8= +go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs= +go.opentelemetry.io/otel/sdk/metric v1.26.0 h1:cWSks5tfriHPdWFnl+qpX3P681aAYqlZHcAyHw5aU9Y= +go.opentelemetry.io/otel/sdk/metric v1.26.0/go.mod h1:ClMFFknnThJCksebJwz7KIyEDHO+nTB6gK8obLy8RyE= +go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA= +go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= +go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= +golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= +golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= +golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= +google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= +google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= +google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= +google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= +google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= +google.golang.org/api v0.162.0 h1:Vhs54HkaEpkMBdgGdOT2P6F0csGG/vxDS0hWHJzmmps= +google.golang.org/api v0.162.0/go.mod h1:6SulDkfoBIg4NFmCuZ39XeeAgSHCPecfSUuDyYlAHs0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= +google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= +google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= +google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= +google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= +google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= +google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= +google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= +google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/steampipe-plugin-googleworkspace/googleworkspace/connection_config.go b/steampipe-plugin-googleworkspace/googleworkspace/connection_config.go new file mode 100644 index 00000000..0d1d42cf --- /dev/null +++ b/steampipe-plugin-googleworkspace/googleworkspace/connection_config.go @@ -0,0 +1,25 @@ +package googleworkspace + +import ( + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" +) + +type googleworkspaceConfig struct { + CredentialFile *string `hcl:"credential_file"` + Credentials *string `hcl:"credentials"` + ImpersonatedUserEmail *string `hcl:"impersonated_user_email"` + TokenPath *string `hcl:"token_path"` +} + +func ConfigInstance() interface{} { + return &googleworkspaceConfig{} +} + +// GetConfig :: retrieve and cast connection config from query data +func GetConfig(connection *plugin.Connection) googleworkspaceConfig { + if connection == nil || connection.Config == nil { + return googleworkspaceConfig{} + } + config, _ := connection.Config.(googleworkspaceConfig) + return config +} diff --git a/steampipe-plugin-googleworkspace/googleworkspace/not_found.go b/steampipe-plugin-googleworkspace/googleworkspace/not_found.go new file mode 100644 index 00000000..5d11e5b2 --- /dev/null +++ b/steampipe-plugin-googleworkspace/googleworkspace/not_found.go @@ -0,0 +1,18 @@ +package googleworkspace + +import ( + "github.com/turbot/go-kit/helpers" + "github.com/turbot/go-kit/types" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" + "google.golang.org/api/googleapi" +) + +// function which returns an IsNotFoundErrorPredicate for Google Workspace API calls +func isNotFoundError(notFoundErrors []string) plugin.ErrorPredicate { + return func(err error) bool { + if gerr, ok := err.(*googleapi.Error); ok { + return helpers.StringSliceContains(notFoundErrors, types.ToString(gerr.Code)) + } + return false + } +} diff --git a/steampipe-plugin-googleworkspace/googleworkspace/plugin.go b/steampipe-plugin-googleworkspace/googleworkspace/plugin.go new file mode 100644 index 00000000..2e4c2f1c --- /dev/null +++ b/steampipe-plugin-googleworkspace/googleworkspace/plugin.go @@ -0,0 +1,48 @@ +/* +Package googleworkspace implements a steampipe plugin for googleworkspace. + +This plugin provides data that Steampipe uses to present foreign +tables that represent Google Workspace resources. +*/ +package googleworkspace + +import ( + "context" + + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" +) + +const pluginName = "steampipe-plugin-googleworkspace" + +// Plugin creates this (googleworkspace) plugin +func Plugin(ctx context.Context) *plugin.Plugin { + p := &plugin.Plugin{ + Name: pluginName, + DefaultTransform: transform.FromCamel().NullIfZero(), + DefaultGetConfig: &plugin.GetConfig{ + ShouldIgnoreError: isNotFoundError([]string{"404"}), + }, + ConnectionConfigSchema: &plugin.ConnectionConfigSchema{ + NewInstance: ConfigInstance, + }, + TableMap: map[string]*plugin.Table{ + "googleworkspace_calendar": tableGoogleWorkspaceCalendar(ctx), + "googleworkspace_calendar_event": tableGoogleWorkspaceCalendarEvent(ctx), + "googleworkspace_calendar_my_event": tableGoogleWorkspaceCalendarMyEvent(ctx), + "googleworkspace_drive": tableGoogleWorkspaceDrive(ctx), + "googleworkspace_drive_my_file": tableGoogleWorkspaceDriveMyFile(ctx), + "googleworkspace_gmail_draft": tableGoogleWorkspaceGmailDraft(ctx), + "googleworkspace_gmail_message": tableGoogleWorkspaceGmailMessage(ctx), + "googleworkspace_gmail_my_draft": tableGoogleWorkspaceGmailMyDraft(ctx), + "googleworkspace_gmail_my_message": tableGoogleWorkspaceGmailMyMessage(ctx), + "googleworkspace_gmail_my_settings": tableGoogleWorkspaceGmailMySettings(ctx), + "googleworkspace_gmail_settings": tableGoogleWorkspaceGmailSettings(ctx), + "googleworkspace_people_contact": tableGoogleWorkspacePeopleContact(ctx), + "googleworkspace_people_contact_group": tableGoogleWorkspacePeopleContactGroup(ctx), + "googleworkspace_people_directory_people": tableGoogleWorkspacePeopleDirectoryPeople(ctx), + }, + } + + return p +} diff --git a/steampipe-plugin-googleworkspace/googleworkspace/service.go b/steampipe-plugin-googleworkspace/googleworkspace/service.go new file mode 100644 index 00000000..935e4408 --- /dev/null +++ b/steampipe-plugin-googleworkspace/googleworkspace/service.go @@ -0,0 +1,220 @@ +package googleworkspace + +import ( + "context" + "errors" + + "golang.org/x/oauth2" + "golang.org/x/oauth2/google" + "google.golang.org/api/calendar/v3" + "google.golang.org/api/drive/v3" + "google.golang.org/api/gmail/v1" + "google.golang.org/api/option" + "google.golang.org/api/people/v1" + + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" +) + +func CalendarService(ctx context.Context, d *plugin.QueryData) (*calendar.Service, error) { + // have we already created and cached the service? + serviceCacheKey := "googleworkspace.calendar" + if cachedData, ok := d.ConnectionManager.Cache.Get(serviceCacheKey); ok { + return cachedData.(*calendar.Service), nil + } + + // so it was not in cache - create service + opts, err := getSessionConfig(ctx, d) + if err != nil { + return nil, err + } + + // Create service + svc, err := calendar.NewService(ctx, opts...) + if err != nil { + return nil, err + } + + // cache the service + d.ConnectionManager.Cache.Set(serviceCacheKey, svc) + + return svc, nil +} + +func PeopleService(ctx context.Context, d *plugin.QueryData) (*people.Service, error) { + // have we already created and cached the service? + serviceCacheKey := "googleworkspace.people" + if cachedData, ok := d.ConnectionManager.Cache.Get(serviceCacheKey); ok { + return cachedData.(*people.Service), nil + } + + // so it was not in cache - create service + opts, err := getSessionConfig(ctx, d) + if err != nil { + return nil, err + } + + // Create service + svc, err := people.NewService(ctx, opts...) + if err != nil { + return nil, err + } + + // cache the service + d.ConnectionManager.Cache.Set(serviceCacheKey, svc) + + return svc, nil +} + +func DriveService(ctx context.Context, d *plugin.QueryData) (*drive.Service, error) { + // have we already created and cached the service? + serviceCacheKey := "googleworkspace.drive" + if cachedData, ok := d.ConnectionManager.Cache.Get(serviceCacheKey); ok { + return cachedData.(*drive.Service), nil + } + + // so it was not in cache - create service + opts, err := getSessionConfig(ctx, d) + if err != nil { + return nil, err + } + + // Create service + svc, err := drive.NewService(ctx, opts...) + if err != nil { + return nil, err + } + + // cache the service + d.ConnectionManager.Cache.Set(serviceCacheKey, svc) + + return svc, nil +} + +func GmailService(ctx context.Context, d *plugin.QueryData) (*gmail.Service, error) { + // have we already created and cached the service? + serviceCacheKey := "googleworkspace.gmail" + if cachedData, ok := d.ConnectionManager.Cache.Get(serviceCacheKey); ok { + return cachedData.(*gmail.Service), nil + } + + // so it was not in cache - create service + opts, err := getSessionConfig(ctx, d) + if err != nil { + return nil, err + } + + // Create service + svc, err := gmail.NewService(ctx, opts...) + if err != nil { + return nil, err + } + + // cache the service + d.ConnectionManager.Cache.Set(serviceCacheKey, svc) + + return svc, nil +} + +func getSessionConfig(ctx context.Context, d *plugin.QueryData) ([]option.ClientOption, error) { + opts := []option.ClientOption{} + + // Get credential file path, and user to impersonate from config (if mentioned) + var credentialContent, tokenPath string + googleworkspaceConfig := GetConfig(d.Connection) + + // 'credential_file' in connection config is DEPRECATED, and will be removed in future release + // use `credentials` instead + if googleworkspaceConfig.Credentials != nil { + credentialContent = *googleworkspaceConfig.Credentials + } else if googleworkspaceConfig.CredentialFile != nil { + credentialContent = *googleworkspaceConfig.CredentialFile + } + + if googleworkspaceConfig.TokenPath != nil { + tokenPath = *googleworkspaceConfig.TokenPath + } + + // If credential path provided, use domain-wide delegation + if credentialContent != "" { + ts, err := getTokenSource(ctx, d) + if err != nil { + return nil, err + } + opts = append(opts, option.WithTokenSource(ts)) + return opts, nil + } + + // If token path provided, authenticate using OAuth 2.0 + if tokenPath != "" { + path, err := expandPath(tokenPath) + if err != nil { + return nil, err + } + opts = append(opts, option.WithCredentialsFile(path)) + return opts, nil + } + + return nil, nil +} + +// Returns a JWT TokenSource using the configuration and the HTTP client from the provided context. +func getTokenSource(ctx context.Context, d *plugin.QueryData) (oauth2.TokenSource, error) { + // Note: based on https://developers.google.com/admin-sdk/directory/v1/guides/delegation#go + + // have we already created and cached the token? + cacheKey := "googleworkspace.token_source" + if ts, ok := d.ConnectionManager.Cache.Get(cacheKey); ok { + return ts.(oauth2.TokenSource), nil + } + + // Get credential file path, and user to impersonate from config (if mentioned) + var impersonateUser string + googleworkspaceConfig := GetConfig(d.Connection) + + // Read credential from JSON string, or from the given path + // NOTE: 'credential_file' in connection config is DEPRECATED, and will be removed in future release + // use `credentials` instead + var creds string + if googleworkspaceConfig.Credentials != nil { + creds = *googleworkspaceConfig.Credentials + } else if googleworkspaceConfig.CredentialFile != nil { + creds = *googleworkspaceConfig.CredentialFile + } + + // Read credential from JSON string, or from the given path + credentialContent, err := pathOrContents(creds) + if err != nil { + return nil, err + } + + if googleworkspaceConfig.ImpersonatedUserEmail != nil { + impersonateUser = *googleworkspaceConfig.ImpersonatedUserEmail + } + + // Return error, since impersonation required to authenticate using domain-wide delegation + if impersonateUser == "" { + return nil, errors.New("impersonated_user_email must be configured") + } + + // Authorize the request + config, err := google.JWTConfigFromJSON( + []byte(credentialContent), + calendar.CalendarReadonlyScope, + drive.DriveReadonlyScope, + gmail.GmailReadonlyScope, + people.ContactsOtherReadonlyScope, + people.ContactsReadonlyScope, + people.DirectoryReadonlyScope, + ) + if err != nil { + return nil, err + } + config.Subject = impersonateUser + + ts := config.TokenSource(ctx) + + // cache the token source + d.ConnectionManager.Cache.Set(cacheKey, ts) + + return ts, nil +} diff --git a/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_calendar.go b/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_calendar.go new file mode 100644 index 00000000..21e7bf91 --- /dev/null +++ b/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_calendar.go @@ -0,0 +1,80 @@ +package googleworkspace + +import ( + "context" + + "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" +) + +//// TABLE DEFINITION + +func tableGoogleWorkspaceCalendar(_ context.Context) *plugin.Table { + return &plugin.Table{ + Name: "googleworkspace_calendar", + Description: "Metadata of the specified calendar.", + List: &plugin.ListConfig{ + Hydrate: listCalendars, + KeyColumns: plugin.SingleColumn("id"), + ShouldIgnoreError: isNotFoundError([]string{"404"}), + }, + Columns: []*plugin.Column{ + { + Name: "id", + Description: "Identifier of the calendar.", + Type: proto.ColumnType_STRING, + }, + { + Name: "summary", + Description: "Title of the calendar.", + Type: proto.ColumnType_STRING, + }, + { + Name: "timezone", + Description: "The time zone of the calendar.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("TimeZone"), + }, + { + Name: "description", + Description: "Description of the calendar.", + Type: proto.ColumnType_STRING, + }, + { + Name: "etag", + Description: "ETag of the resource.", + Type: proto.ColumnType_STRING, + }, + { + Name: "location", + Description: "Geographic location of the calendar as free-form text.", + Type: proto.ColumnType_STRING, + }, + { + Name: "conference_properties", + Description: "Describes the conferencing properties for this calendar.", + Type: proto.ColumnType_JSON, + }, + }, + } +} + +//// LIST FUNCTION + +func listCalendars(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := CalendarService(ctx, d) + if err != nil { + return nil, err + } + calendarID := d.EqualsQuals["id"].GetStringValue() + + resp, err := service.Calendars.Get(calendarID).Do() + if err != nil { + return nil, err + } + d.StreamListItem(ctx, resp) + + return nil, nil +} diff --git a/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_calendar_event.go b/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_calendar_event.go new file mode 100644 index 00000000..48c6ba75 --- /dev/null +++ b/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_calendar_event.go @@ -0,0 +1,407 @@ +package googleworkspace + +import ( + "context" + "time" + + "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" + + "google.golang.org/api/calendar/v3" +) + +type calendarEvent = struct { + calendar.Event + CalendarId string +} + +func calendarEventColumns() []*plugin.Column { + return []*plugin.Column{ + { + Name: "id", + Description: "Opaque identifier of the event.", + Type: proto.ColumnType_STRING, + }, + { + Name: "summary", + Description: "Specifies the title of the event.", + Type: proto.ColumnType_STRING, + }, + { + Name: "status", + Description: "Status of the event.", + Type: proto.ColumnType_STRING, + }, + { + Name: "calendar_id", + Description: "Identifier of the calendar.", + Type: proto.ColumnType_STRING, + }, + { + Name: "start_time", + Description: "Specifies the event start time.", + Type: proto.ColumnType_TIMESTAMP, + Transform: transform.FromP(formatTimestamp, "StartTime").NullIfZero(), + }, + { + Name: "end_time", + Description: "Specifies the event end time.", + Type: proto.ColumnType_TIMESTAMP, + Transform: transform.FromP(formatTimestamp, "EndTime").NullIfZero(), + }, + { + Name: "day", + Description: "Specifies the day of a week.", + Type: proto.ColumnType_STRING, + Transform: transform.FromP(formatTimestamp, "Day").NullIfZero(), + }, + { + Name: "hangout_link", + Description: "An absolute link to the Google Hangout associated with this event.", + Type: proto.ColumnType_STRING, + }, + { + Name: "event_type", + Description: "Specifies the type of the event.", + Type: proto.ColumnType_STRING, + }, + { + Name: "html_link", + Description: "An absolute link to this event in the Google Calendar Web UI.", + Type: proto.ColumnType_STRING, + }, + { + Name: "attendees_omitted", + Description: "Indicates whether attendees may have been omitted from the event's representation, or not.", + Type: proto.ColumnType_BOOL, + }, + { + Name: "color_id", + Description: "The color of the event.", + Type: proto.ColumnType_STRING, + }, + { + Name: "created_at", + Description: "Creation time of the event.", + Type: proto.ColumnType_TIMESTAMP, + Transform: transform.FromField("Created").NullIfZero(), + }, + { + Name: "description", + Description: "A short user-defined description of the event.", + Type: proto.ColumnType_STRING, + }, + { + Name: "end_time_unspecified", + Description: "Indicates whether the end time is actually unspecified, or not.", + Type: proto.ColumnType_BOOL, + }, + { + Name: "etag", + Description: "ETag of the resource.", + Type: proto.ColumnType_STRING, + }, + { + Name: "guests_can_invite_others", + Description: "Indicates whether attendees other than the organizer can invite others to the event, or not.", + Type: proto.ColumnType_BOOL, + Default: true, + }, + { + Name: "guests_can_modify", + Description: "Indicates whether attendees other than the organizer can modify the event, or not.", + Type: proto.ColumnType_BOOL, + Default: false, + }, + { + Name: "guests_can_see_other_guests", + Description: "Indicates whether attendees other than the organizer can modify the event, or not.", + Type: proto.ColumnType_BOOL, + Default: true, + }, + { + Name: "ical_uid", + Description: "Specifies the event unique identifier as defined in RFC5545. It is used to uniquely identify events accross calendaring systems and must be supplied when importing events via the import method.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("ICalUID"), + }, + { + Name: "location", + Description: "Geographic location of the event as free-form text.", + Type: proto.ColumnType_STRING, + }, + { + Name: "locked", + Description: "Indicates whether this is a locked event copy where no changes can be made to the main event fields \"summary\", \"description\", \"location\", \"start\", \"end\" or \"recurrence\".", + Type: proto.ColumnType_BOOL, + Default: false, + }, + { + Name: "private_copy", + Description: "Indicates whether event propagation is disabled, or not.", + Type: proto.ColumnType_BOOL, + Default: false, + }, + { + Name: "query", + Description: "Filter string to filter events.", + Type: proto.ColumnType_STRING, + Transform: transform.FromQual("query"), + }, + { + Name: "recurring_event_id", + Description: "For an instance of a recurring event, this is the id of the recurring event to which this instance belongs.", + Type: proto.ColumnType_STRING, + }, + { + Name: "sequence", + Description: "Sequence number as per iCalendar.", + Type: proto.ColumnType_INT, + }, + { + Name: "timezone", + Description: "The time zone of the calendar.", + Type: proto.ColumnType_STRING, + }, + { + Name: "transparency", + Description: "Indicates whether the event blocks time on the calendar.", + Type: proto.ColumnType_STRING, + }, + { + Name: "updated_at", + Description: "Last modification time of the event.", + Type: proto.ColumnType_TIMESTAMP, + Transform: transform.FromField("Updated").NullIfZero(), + }, + { + Name: "visibility", + Description: "Visibility of the event.", + Type: proto.ColumnType_STRING, + }, + { + Name: "attachments", + Description: "A list of file attachments for the event.", + Type: proto.ColumnType_JSON, + }, + { + Name: "attendees", + Description: "A list of attendees of the event.", + Type: proto.ColumnType_JSON, + }, + { + Name: "conference_data", + Description: "The conference-related information, such as details of a Google Meet conference.", + Type: proto.ColumnType_JSON, + }, + { + Name: "creator", + Description: "Specifies the creator details of the event.", + Type: proto.ColumnType_JSON, + }, + { + Name: "extended_properties", + Description: "A list of extended properties of the event.", + Type: proto.ColumnType_JSON, + }, + { + Name: "organizer", + Description: "Specifies the organizer details of the event.", + Type: proto.ColumnType_JSON, + }, + { + Name: "original_start_time", + Description: "For an instance of a recurring event, this is the time at which this event would start according to the recurrence data in the recurring event identified by recurringEventId.", + Type: proto.ColumnType_JSON, + }, + { + Name: "recurrence", + Description: "A list of RRULE, EXRULE, RDATE and EXDATE lines for a recurring event, as specified in RFC5545.", + Type: proto.ColumnType_JSON, + }, + { + Name: "reminders", + Description: "Information about the event's reminders for the authenticated user.", + Type: proto.ColumnType_JSON, + }, + { + Name: "source", + Description: "Source from which the event was created.", + Type: proto.ColumnType_JSON, + }, + } +} + +//// TABLE DEFINITION + +func tableGoogleWorkspaceCalendarEvent(_ context.Context) *plugin.Table { + return &plugin.Table{ + Name: "googleworkspace_calendar_event", + Description: "Events scheduled on the specified calendar.", + List: &plugin.ListConfig{ + Hydrate: listCalendarEvents, + ShouldIgnoreError: isNotFoundError([]string{"404"}), + KeyColumns: []*plugin.KeyColumn{ + { + Name: "calendar_id", + Require: plugin.Required, + }, + { + Name: "query", + Require: plugin.Optional, + }, + { + Name: "start_time", + Require: plugin.Optional, + Operators: []string{">", ">=", "=", "<", "<="}, + }, + }, + }, + Get: &plugin.GetConfig{ + KeyColumns: plugin.AllColumns([]string{"calendar_id", "id"}), + Hydrate: getCalendarEvent, + }, + Columns: calendarEventColumns(), + } +} + +//// LIST FUNCTION + +func listCalendarEvents(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := CalendarService(ctx, d) + if err != nil { + return nil, err + } + calendarID := d.EqualsQuals["calendar_id"].GetStringValue() + + // By default, API can return maximum 2500 records in a single page + maxResult := int64(2500) + // Reduce the basic request limit down if the user has only requested a small number of rows + limit := d.QueryContext.Limit + if d.QueryContext.Limit != nil { + if *limit < maxResult { + maxResult = *limit + } + } + + // Free text search terms to find events that match these terms in any field, except for extended properties + var query string + if d.EqualsQuals["query"] != nil { + query = d.EqualsQuals["query"].GetStringValue() + } + + resp := service.Events.List(calendarID).ShowDeleted(false).SingleEvents(true).Q(query).MaxResults(maxResult) + if d.Quals["start_time"] != nil { + for _, q := range d.Quals["start_time"].Quals { + givenTime := q.Value.GetTimestampValue().AsTime() + beforeTime := givenTime.Add(time.Duration(-1) * time.Second).Format("2006-01-02T15:04:05.000Z") + afterTime := givenTime.Add(time.Second * 1).Format("2006-01-02T15:04:05.000Z") + + switch q.Operator { + case ">": + resp.TimeMin(afterTime) + case ">=": + resp.TimeMin(givenTime.Format("2006-01-02T15:04:05.000Z")) + case "=": + resp.TimeMin(givenTime.Format("2006-01-02T15:04:05.000Z")).TimeMax(givenTime.Format("2006-01-02T15:04:05.000Z")) + case "<=": + resp.TimeMax(givenTime.Format("2006-01-02T15:04:05.000Z")) + case "<": + resp.TimeMax(beforeTime) + } + } + } + if err := resp.Pages(ctx, func(page *calendar.Events) error { + for _, event := range page.Items { + d.StreamListItem(ctx, calendarEvent{*event, calendarID}) + + // Context can be cancelled due to manual cancellation or the limit has been hit + if plugin.IsCancelled(ctx) { + page.NextPageToken = "" + break + } + } + return nil + }); err != nil { + return nil, err + } + + return nil, nil +} + +//// HYDRATE FUNCTIONS + +func getCalendarEvent(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := CalendarService(ctx, d) + if err != nil { + return nil, err + } + calendarID := d.EqualsQuals["calendar_id"].GetStringValue() + eventID := d.EqualsQuals["id"].GetStringValue() + + // Return nil, if no input provided + if calendarID == "" || eventID == "" { + return nil, nil + } + + resp, err := service.Events.Get(calendarID, eventID).Do() + if err != nil { + return nil, err + } + + return calendarEvent{*resp, calendarID}, err +} + +//// TRANSFORM FUNCTIONS + +func formatTimestamp(_ context.Context, d *transform.TransformData) (interface{}, error) { + data := d.HydrateItem.(calendarEvent) + param := d.Param.(string) + var startTime, endTime string + + // handling empty data + if data.Start == nil || data.End == nil { + return nil, nil + } + + // If the event is an all-day event, response includes only + // `Start.DateTime` and `End.DateTime`, and the value consists + // only date, for example `2021-08-15` + // Following transformation is used to parse the date in consistent format, i.e. RFC3339 + startTime = data.Start.DateTime + if startTime == "" { + startTime = parseTime(data.Start.Date) + } + + endTime = data.End.DateTime + if endTime == "" { + endTime = parseTime(data.End.Date) + } + + t, err := time.Parse(time.RFC3339, startTime) + if err != nil { + return nil, err + } + + formattedTime := map[string]string{ + "StartTime": startTime, + "EndTime": endTime, + "Day": t.Weekday().String(), + } + + return formattedTime[param], nil +} + +func parseTime(timeInString string) string { + if timeInString == "" { + return "" + } + parsedDate, err := time.Parse("2006-01-02", timeInString) + if err != nil { + return "" + } + return parsedDate.Format("2006-01-02T15:04:05.000Z") +} diff --git a/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_calendar_my_event.go b/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_calendar_my_event.go new file mode 100644 index 00000000..baece8f6 --- /dev/null +++ b/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_calendar_my_event.go @@ -0,0 +1,99 @@ +package googleworkspace + +import ( + "context" + "time" + + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" + + "google.golang.org/api/calendar/v3" +) + +//// TABLE DEFINITION + +func tableGoogleWorkspaceCalendarMyEvent(_ context.Context) *plugin.Table { + return &plugin.Table{ + Name: "googleworkspace_calendar_my_event", + Description: "Events scheduled on the specified calendar.", + List: &plugin.ListConfig{ + Hydrate: listCalendarMyEvents, + ShouldIgnoreError: isNotFoundError([]string{"404"}), + KeyColumns: []*plugin.KeyColumn{ + { + Name: "query", + Require: plugin.Optional, + }, + { + Name: "start_time", + Require: plugin.Optional, + Operators: []string{">", ">=", "=", "<", "<="}, + }, + }, + }, + Columns: calendarEventColumns(), + } +} + +//// LIST FUNCTION + +func listCalendarMyEvents(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := CalendarService(ctx, d) + if err != nil { + return nil, err + } + + // By default, API can return maximum 2500 records in a single page + maxResult := int64(2500) + // Reduce the basic request limit down if the user has only requested a small number of rows + limit := d.QueryContext.Limit + if d.QueryContext.Limit != nil { + if *limit < maxResult { + maxResult = *limit + } + } + + // Free text search terms to find events that match these terms in any field, except for extended properties + var query string + if d.EqualsQuals["query"] != nil { + query = d.EqualsQuals["query"].GetStringValue() + } + + resp := service.Events.List("primary").ShowDeleted(false).SingleEvents(true).Q(query).MaxResults(maxResult) + if d.Quals["start_time"] != nil { + for _, q := range d.Quals["start_time"].Quals { + givenTime := q.Value.GetTimestampValue().AsTime() + beforeTime := givenTime.Add(time.Duration(-1) * time.Second).Format("2006-01-02T15:04:05.000Z") + afterTime := givenTime.Add(time.Second * 1).Format("2006-01-02T15:04:05.000Z") + + switch q.Operator { + case ">": + resp.TimeMin(afterTime) + case ">=": + resp.TimeMin(givenTime.Format("2006-01-02T15:04:05.000Z")) + case "=": + resp.TimeMin(givenTime.Format("2006-01-02T15:04:05.000Z")).TimeMax(givenTime.Format("2006-01-02T15:04:05.000Z")) + case "<=": + resp.TimeMax(givenTime.Format("2006-01-02T15:04:05.000Z")) + case "<": + resp.TimeMax(beforeTime) + } + } + } + if err := resp.Pages(ctx, func(page *calendar.Events) error { + for _, event := range page.Items { + d.StreamListItem(ctx, calendarEvent{*event, page.Summary}) + + // Context can be cancelled due to manual cancellation or the limit has been hit + if plugin.IsCancelled(ctx) { + page.NextPageToken = "" + break + } + } + return nil + }); err != nil { + return nil, err + } + + return nil, err +} diff --git a/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_drive.go b/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_drive.go new file mode 100644 index 00000000..a48511da --- /dev/null +++ b/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_drive.go @@ -0,0 +1,287 @@ +package googleworkspace + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/iancoleman/strcase" + "github.com/turbot/go-kit/helpers" + "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" + + "google.golang.org/api/drive/v3" + "google.golang.org/api/googleapi" +) + +//// TABLE DEFINITION + +func tableGoogleWorkspaceDrive(_ context.Context) *plugin.Table { + return &plugin.Table{ + Name: "googleworkspace_drive", + Description: "Drives defined user's shared drives in the Google Drive.", + List: &plugin.ListConfig{ + Hydrate: listDrives, + KeyColumns: []*plugin.KeyColumn{ + { + Name: "name", + Require: plugin.Optional, + }, + { + Name: "created_time", + Require: plugin.Optional, + Operators: []string{">", ">=", "=", "<", "<="}, + }, + { + Name: "use_domain_admin_access", + Require: plugin.Optional, + }, + { + Name: "query", + Require: plugin.Optional, + }, + }, + }, + Get: &plugin.GetConfig{ + KeyColumns: plugin.SingleColumn("id"), + Hydrate: getDrive, + }, + Columns: []*plugin.Column{ + { + Name: "id", + Description: "The ID of this shared drive which is also the ID of the top level folder of this shared drive.", + Type: proto.ColumnType_STRING, + }, + { + Name: "name", + Description: "The name of this shared drive.", + Type: proto.ColumnType_STRING, + }, + { + Name: "created_time", + Description: "The time at which the shared drive was created.", + Type: proto.ColumnType_TIMESTAMP, + }, + { + Name: "hidden", + Description: "Indicates whether the shared drive is hidden from default view, or not.", + Type: proto.ColumnType_BOOL, + }, + { + Name: "admin_managed_restrictions", + Description: "Indicates whether administrative privileges on this shared drive are required to modify restrictions, or not.", + Type: proto.ColumnType_BOOL, + Transform: transform.FromField("Restrictions.AdminManagedRestrictions"), + }, + { + Name: "background_image_link", + Description: "A short-lived link to this shared drive's background image.", + Type: proto.ColumnType_STRING, + }, + { + Name: "color_rgb", + Description: "The color of this shared drive as an RGB hex string.", + Type: proto.ColumnType_STRING, + }, + { + Name: "copy_requires_writer_permission", + Description: "Indicates whether the options to copy, print, or download files inside this shared drive, should be disabled for readers and commenters, or not.", + Type: proto.ColumnType_BOOL, + Transform: transform.FromField("Restrictions.CopyRequiresWriterPermission"), + }, + { + Name: "domain_users_only", + Description: "Indicates whether access to this shared drive and items inside this shared drive is restricted to users of the domain to which this shared drive belongs.", + Type: proto.ColumnType_BOOL, + Transform: transform.FromField("Restrictions.DomainUsersOnly"), + }, + { + Name: "drive_members_only", + Description: "Indicates whether access to items inside this shared drive is restricted to its members, or not.", + Type: proto.ColumnType_BOOL, + Transform: transform.FromField("Restrictions.DriveMembersOnly"), + }, + { + Name: "theme_id", + Description: "The ID of the theme from which the background image and color will be set.", + Type: proto.ColumnType_STRING, + }, + { + Name: "use_domain_admin_access", + Description: "Issue the request as a domain administrator; if set to true, then all shared drives of the domain in which the requester is an administrator are returned. Please refer Refer https://developers.google.com/drive/api/v3/ref-search-terms#drive_properties.", + Type: proto.ColumnType_BOOL, + Transform: transform.FromQual("use_domain_admin_access"), + }, + { + Name: "query", + Description: "Query string for [searching](https://developers.google.com/drive/api/v3/ref-search-terms#drive_properties) shared drives.", + Type: proto.ColumnType_STRING, + Transform: transform.FromQual("query"), + }, + { + Name: "background_image_file", + Description: "An image file and cropping parameters from which a background image for this shared drive is set.", + Type: proto.ColumnType_JSON, + }, + { + Name: "capabilities", + Description: "Describes the capabilities the current user has on this shared drive.", + Type: proto.ColumnType_JSON, + }, + }, + } +} + +//// LIST FUNCTION + +func listDrives(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := DriveService(ctx, d) + if err != nil { + return nil, err + } + equalQuals := d.EqualsQuals + + var queryFilter, query string + var filter []string + + if equalQuals["name"] != nil { + filter = append(filter, fmt.Sprintf("%s = \"%s\"", "name", equalQuals["name"].GetStringValue())) + } + + if d.Quals["created_time"] != nil { + for _, q := range d.Quals["created_time"].Quals { + givenTime := q.Value.GetTimestampValue().AsTime() + beforeTime := givenTime.Add(time.Duration(-1) * time.Second).Format("2006-01-02T15:04:05.000Z") + afterTime := givenTime.Add(time.Second * 1).Format("2006-01-02T15:04:05.000Z") + + // Since, the query filter matches the actual time + switch q.Operator { + case ">", "<": + filter = append(filter, fmt.Sprintf("%s %s \"%s\"", "createdTime", q.Operator, givenTime.Format("2006-01-02T15:04:05.000Z"))) + case "=": + filter = append(filter, fmt.Sprintf("createdTime > \"%s\" and createdTime < \"%s\"", beforeTime, afterTime)) + case ">=": + filter = append(filter, fmt.Sprintf("%s > \"%s\"", "createdTime", beforeTime)) + case "<=": + filter = append(filter, fmt.Sprintf("%s < \"%s\"", "createdTime", afterTime)) + } + } + } + + // Query string for searching shared drives. Refer https://developers.google.com/drive/api/v3/ref-search-terms#drive_properties + // For example, "hidden=true" + if equalQuals["query"] != nil { + queryFilter = equalQuals["query"].GetStringValue() + } + + if queryFilter != "" { + query = queryFilter + } else if len(filter) > 0 { + query = strings.Join(filter, " and ") + } + + // Check for query context and requests only for queried columns + givenColumns := d.QueryContext.Columns + requiredFields := buildDriveRequestFields(ctx, givenColumns) + + // Set default as false + // Need to set true for some of the query terms, i.e. when filtering using createdTime, memberCount, name, or organizerCount + // Refer https://developers.google.com/drive/api/v3/ref-search-terms#drive_properties + var useDomainAdminAccess bool + if equalQuals["use_domain_admin_access"] != nil { + useDomainAdminAccess = equalQuals["use_domain_admin_access"].GetBoolValue() + } + + // By default, API can return maximum 100 records in a single page + pageSize := int64(100) + + limit := d.QueryContext.Limit + if d.QueryContext.Limit != nil { + if *limit < pageSize { + pageSize = *limit + } + } + + resp := service.Drives.List().Fields(requiredFields...).Q(query).UseDomainAdminAccess(useDomainAdminAccess).PageSize(pageSize) + if err := resp.Pages(ctx, func(page *drive.DriveList) error { + for _, data := range page.Drives { + parsedTime, _ := time.Parse(time.RFC3339, data.CreatedTime) + data.CreatedTime = parsedTime.Format(time.RFC3339) + d.StreamListItem(ctx, data) + + // Context can be cancelled due to manual cancellation or the limit has been hit + if plugin.IsCancelled(ctx) { + page.NextPageToken = "" + break + } + } + return nil + }); err != nil { + return nil, err + } + + return nil, err +} + +//// HYDRATE FUNCTIONS + +func getDrive(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + plugin.Logger(ctx).Trace("getDrive") + + // Create service + service, err := DriveService(ctx, d) + if err != nil { + return nil, err + } + id := d.EqualsQuals["id"].GetStringValue() + + // Return nil, if no input provided + if id == "" { + return nil, nil + } + + // Check for query context and requests only for queried columns + givenColumns := d.QueryContext.Columns + requiredFields := buildDriveRequestFields(ctx, givenColumns) + + resp, err := service.Drives.Get(id).Fields(requiredFields...).Do() + if err != nil { + return nil, err + } + + return resp, nil +} + +// buildDriveRequestFields :: Return columns passed in query context +func buildDriveRequestFields(ctx context.Context, queryColumns []string) []googleapi.Field { + var fields []string + var requestedFields []googleapi.Field + + // Since ID is unique, always add in the requested field + if !helpers.StringSliceContains(queryColumns, "id") { + queryColumns = append(queryColumns, "id") + } + + for _, columnName := range queryColumns { + // Optional columns + if columnName == "query" || columnName == "use_domain_admin_access" || columnName == "_ctx" { + continue + } + + formattedColumnName := strcase.ToLowerCamel(columnName) + switch columnName { + case "admin_managed_restrictions", "copy_requires_writer_permission", "domain_users_only", "drive_members_only": + fields = append(fields, "restrictions/"+formattedColumnName) + default: + fields = append(fields, formattedColumnName) + } + } + + givenFields := strings.Join(fields, ", ") + requestedFields = append(requestedFields, googleapi.Field(fmt.Sprintf("nextPageToken, drives(%s)", givenFields))) + + return requestedFields +} diff --git a/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_drive_my_file.go b/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_drive_my_file.go new file mode 100644 index 00000000..51dd0a22 --- /dev/null +++ b/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_drive_my_file.go @@ -0,0 +1,506 @@ +package googleworkspace + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/iancoleman/strcase" + "github.com/turbot/go-kit/helpers" + "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" + + "google.golang.org/api/drive/v3" + "google.golang.org/api/googleapi" +) + +//// TABLE DEFINITION + +func driveFileColumns() []*plugin.Column { + return []*plugin.Column{ + { + Name: "id", + Description: "The ID of the file.", + Type: proto.ColumnType_STRING, + }, + { + Name: "name", + Description: "Specifies the name of the file.", + Type: proto.ColumnType_STRING, + }, + { + Name: "mime_type", + Description: "The MIME type of the file. Google Drive will attempt to automatically detect an appropriate value from uploaded content if no value is provided.", + Type: proto.ColumnType_STRING, + }, + { + Name: "drive_id", + Description: "ID of the shared drive the file resides in.", + Type: proto.ColumnType_STRING, + }, + { + Name: "owned_by_me", + Description: "Indicates whether the user owns the file, or not.", + Type: proto.ColumnType_BOOL, + }, + { + Name: "shared", + Description: "Indicates whether the file has been shared, or not.", + Type: proto.ColumnType_BOOL, + }, + { + Name: "copy_requires_writer_permission", + Description: "Indicates whether the options to copy, print, or download this file, should be disabled for readers and commenters, or not.", + Type: proto.ColumnType_BOOL, + }, + { + Name: "created_time", + Description: "The time at which the file was created.", + Type: proto.ColumnType_TIMESTAMP, + }, + { + Name: "description", + Description: "A short description of the file.", + Type: proto.ColumnType_STRING, + }, + { + Name: "explicitly_trashed", + Description: "Indicates whether the file has been explicitly trashed, as opposed to recursively trashed from a parent folder.", + Type: proto.ColumnType_BOOL, + }, + { + Name: "file_extension", + Description: "The final component of fullFileExtension.", + Type: proto.ColumnType_STRING, + }, + { + Name: "folder_color_rgb", + Description: "The color for a folder or shortcut to a folder as an RGB hex string.", + Type: proto.ColumnType_STRING, + }, + { + Name: "full_file_extension", + Description: "The full file extension extracted from the name field.", + Type: proto.ColumnType_STRING, + }, + { + Name: "has_augmented_permissions", + Description: "Indicates whether there are permissions directly on this file, or not.", + Type: proto.ColumnType_BOOL, + }, + { + Name: "has_thumbnail", + Description: "Indicates whether this file has a thumbnail, or not.", + Type: proto.ColumnType_BOOL, + }, + { + Name: "head_revision_id", + Description: "The ID of the file's head revision.", + Type: proto.ColumnType_STRING, + }, + { + Name: "icon_link", + Description: "A static, unauthenticated link to the file's icon.", + Type: proto.ColumnType_STRING, + }, + { + Name: "is_app_authorized", + Description: "Indicates whether the file was created or opened by the requesting app, or not.", + Type: proto.ColumnType_BOOL, + }, + { + Name: "md5_checksum", + Description: "The MD5 checksum for the content of the file.", + Type: proto.ColumnType_STRING, + }, + { + Name: "modified_by_me", + Description: "Indicates whether the file has been modified by this user, or not.", + Type: proto.ColumnType_BOOL, + }, + { + Name: "modified_by_me_time", + Description: "The last time the file was modified by the use.", + Type: proto.ColumnType_TIMESTAMP, + }, + { + Name: "modified_time", + Description: "The last time the file was modified by anyone.", + Type: proto.ColumnType_TIMESTAMP, + }, + { + Name: "original_file_name", + Description: "The original filename of the uploaded content if available, or else the original value of the name field.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("OriginalFilename").NullIfZero(), + }, + { + Name: "query", + Description: "A search query combining one or more search terms to [filter](https://developers.google.com/drive/api/v3/search-files) the file results.", + Type: proto.ColumnType_STRING, + Transform: transform.FromQual("query"), + }, + { + Name: "quota_bytes_used", + Description: "The number of storage quota bytes used by the file.", + Type: proto.ColumnType_INT, + }, + { + Name: "resource_key", + Description: "A key needed to access the item via a shared link.", + Type: proto.ColumnType_STRING, + }, + { + Name: "shared_with_me_time", + Description: "The time at which the file was shared with the user.", + Type: proto.ColumnType_TIMESTAMP, + }, + { + Name: "size", + Description: "The size of the file's content in bytes.", + Type: proto.ColumnType_INT, + }, + { + Name: "starred", + Description: "Indicates whether the user has starred the file, or not.", + Type: proto.ColumnType_BOOL, + }, + { + Name: "thumbnail_link", + Description: "A short-lived link to the file's thumbnail, if available.", + Type: proto.ColumnType_STRING, + }, + { + Name: "thumbnail_version", + Description: "The thumbnail version for use in thumbnail cache invalidation.", + Type: proto.ColumnType_INT, + }, + { + Name: "trashed", + Description: "Indicates whether the file has been trashed, either explicitly or from a trashed parent folder, or not.", + Type: proto.ColumnType_BOOL, + }, + { + Name: "trashed_time", + Description: "The time that the item was trashed.", + Type: proto.ColumnType_TIMESTAMP, + }, + { + Name: "version", + Description: "A monotonically increasing version number for the file.", + Type: proto.ColumnType_INT, + }, + { + Name: "viewed_by_me", + Description: "Indicates whether the the file has been viewed by this user, or not.", + Type: proto.ColumnType_BOOL, + }, + { + Name: "viewed_by_me_time", + Description: "The last time the file was viewed by the user.", + Type: proto.ColumnType_TIMESTAMP, + }, + { + Name: "web_content_link", + Description: "A link for downloading the content of the file in a browser.", + Type: proto.ColumnType_STRING, + }, + { + Name: "web_view_link", + Description: "A link for opening the file in a relevant Google editor or viewer in a browser.", + Type: proto.ColumnType_STRING, + }, + { + Name: "writers_can_share", + Description: "Indicates whether users with only writer permission can modify the file's permissions, or not.", + Type: proto.ColumnType_BOOL, + }, + { + Name: "app_properties", + Description: "A collection of arbitrary key-value pairs which are private to the requesting app.", + Type: proto.ColumnType_JSON, + }, + { + Name: "capabilities", + Description: "Describes capabilities the current user has on this file.", + Type: proto.ColumnType_JSON, + }, + { + Name: "content_hints", + Description: "Additional information about the content of the file.", + Type: proto.ColumnType_JSON, + }, + { + Name: "content_restrictions", + Description: "Restrictions for accessing the content of the file.", + Type: proto.ColumnType_JSON, + }, + { + Name: "export_links", + Description: "Links for exporting Docs Editors files to specific formats.", + Type: proto.ColumnType_JSON, + }, + { + Name: "image_media_metadata", + Description: "Additional metadata about image media, if available.", + Type: proto.ColumnType_JSON, + }, + { + Name: "last_modifying_user", + Description: "The last user to modify the file.", + Type: proto.ColumnType_JSON, + }, + { + Name: "link_share_metadata", + Description: "Contains details about the link URLs that clients are using to refer to this item.", + Type: proto.ColumnType_JSON, + }, + { + Name: "owners", + Description: "The owner of this file. Only certain legacy files may have more than one owner.", + Type: proto.ColumnType_JSON, + }, + { + Name: "parents", + Description: "The IDs of the parent folders which contain the file.", + Type: proto.ColumnType_JSON, + }, + { + Name: "permission_ids", + Description: "List of permission IDs for users with access to this file.", + Type: proto.ColumnType_JSON, + }, + { + Name: "permissions", + Description: "The full list of permissions for the file.", + Type: proto.ColumnType_JSON, + }, + { + Name: "properties", + Description: "A collection of arbitrary key-value pairs which are visible to all apps.", + Type: proto.ColumnType_JSON, + }, + { + Name: "sharing_user", + Description: "The user who shared the file with the requesting user, if applicable.", + Type: proto.ColumnType_JSON, + }, + { + Name: "shortcut_details", + Description: "Shortcut file details. Only populated for shortcut files, which have the mimeType field set to application/vnd.google-apps.shortcut.", + Type: proto.ColumnType_JSON, + }, + { + Name: "spaces", + Description: "The list of spaces which contain the file.", + Type: proto.ColumnType_JSON, + }, + { + Name: "trashing_user", + Description: "Specifies the user who trashed the file explicitly.", + Type: proto.ColumnType_JSON, + }, + { + Name: "video_media_metadata", + Description: "Additional metadata about video media.", + Type: proto.ColumnType_JSON, + }, + } +} + +func tableGoogleWorkspaceDriveMyFile(_ context.Context) *plugin.Table { + return &plugin.Table{ + Name: "googleworkspace_drive_my_file", + Description: "Retrieves file's metadata or content owned by an user.", + List: &plugin.ListConfig{ + Hydrate: listDriveMyFiles, + KeyColumns: []*plugin.KeyColumn{ + { + Name: "name", + Require: plugin.Optional, + }, + { + Name: "created_time", + Require: plugin.Optional, + Operators: []string{">", ">=", "=", "<", "<="}, + }, + { + Name: "mime_type", + Require: plugin.Optional, + Operators: []string{"=", "<>", "!="}, + }, + { + Name: "query", + Require: plugin.Optional, + }, + }, + }, + Get: &plugin.GetConfig{ + KeyColumns: plugin.SingleColumn("id"), + Hydrate: getDriveMyFile, + }, + Columns: driveFileColumns(), + } +} + +//// LIST FUNCTION + +func listDriveMyFiles(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := DriveService(ctx, d) + if err != nil { + return nil, err + } + + equalQuals := d.EqualsQuals + quals := d.Quals + + var queryFilter, query string + var filter []string + + if equalQuals["name"] != nil { + filter = append(filter, fmt.Sprintf("%s = \"%s\"", "name", equalQuals["name"].GetStringValue())) + } + + if quals["created_time"] != nil { + for _, q := range quals["created_time"].Quals { + givenTime := q.Value.GetTimestampValue().AsTime() + beforeTime := givenTime.Add(time.Duration(-1) * time.Second).Format("2006-01-02T15:04:05.000Z") + afterTime := givenTime.Add(time.Second * 1).Format("2006-01-02T15:04:05.000Z") + + // Since, the query filter matches the actual time + switch q.Operator { + case ">", "<": + filter = append(filter, fmt.Sprintf("%s %s \"%s\"", "createdTime", q.Operator, givenTime.Format("2006-01-02T15:04:05.000Z"))) + case "=": + filter = append(filter, fmt.Sprintf("createdTime > \"%s\" and createdTime < \"%s\"", beforeTime, afterTime)) + case ">=": + filter = append(filter, fmt.Sprintf("%s > \"%s\"", "createdTime", beforeTime)) + case "<=": + filter = append(filter, fmt.Sprintf("%s < \"%s\"", "createdTime", afterTime)) + } + } + } + + if quals["mime_type"] != nil { + for _, q := range quals["mime_type"].Quals { + mimeType := q.Value.GetStringValue() + + switch q.Operator { + case "=": + filter = append(filter, fmt.Sprintf("%s = \"%s\"", "mimeType", mimeType)) + case "!=", "<>": + filter = append(filter, fmt.Sprintf("%s != \"%s\"", "mimeType", mimeType)) + } + } + } + + // Query string for searching files. Refer https://developers.google.com/drive/api/v3/search-files + // For example, "name contains 'steampipe'", returns all the files containing the word 'steampipe' + if equalQuals["query"] != nil { + queryFilter = equalQuals["query"].GetStringValue() + } + + if queryFilter != "" { + query = queryFilter + } else if len(filter) > 0 { + query = strings.Join(filter, " and ") + } + + // Check for query context and requests only for queried columns + givenColumns := d.QueryContext.Columns + requiredFields := buildDriveFileRequestFields(ctx, givenColumns) + + // By default, API can return maximum 1000 records in a single page + maxResult := int64(1000) + + limit := d.QueryContext.Limit + if d.QueryContext.Limit != nil { + if *limit < maxResult { + maxResult = *limit + } + } + + // Use "*" to return all fields + resp := service.Files.List().Fields(requiredFields...).Q(query).PageSize(maxResult) + if err := resp.Pages(ctx, func(page *drive.FileList) error { + for _, file := range page.Files { + parsedTime, _ := time.Parse(time.RFC3339, file.CreatedTime) + file.CreatedTime = parsedTime.Format(time.RFC3339) + d.StreamListItem(ctx, file) + + // Context can be cancelled due to manual cancellation or the limit has been hit + if plugin.IsCancelled(ctx) { + page.NextPageToken = "" + break + } + } + return nil + }); err != nil { + return nil, err + } + + return nil, err +} + +//// HYDRATE FUNCTIONS + +func getDriveMyFile(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + plugin.Logger(ctx).Trace("getDriveMyFile") + + // Create service + service, err := DriveService(ctx, d) + if err != nil { + return nil, err + } + fileID := d.EqualsQuals["id"].GetStringValue() + + // Return nil, if no input provided + if fileID == "" { + return nil, nil + } + + // Check for query context and requests only for queried columns + givenColumns := d.QueryContext.Columns + requiredFields := buildDriveFileRequestFields(ctx, givenColumns) + + // Use "*" to return all fields + resp, err := service.Files.Get(fileID).Fields(requiredFields...).Do() + if err != nil { + return nil, err + } + + return resp, nil +} + +// buildDriveFileRequestFields :: Return columns passed in query context +func buildDriveFileRequestFields(ctx context.Context, queryColumns []string) []googleapi.Field { + var fields []string + var requestedFields []googleapi.Field + + // Since ID is unique, always add in the requested field + if !helpers.StringSliceContains(queryColumns, "id") { + queryColumns = append(queryColumns, "id") + } + + for _, columnName := range queryColumns { + // Optional columns + if columnName == "query" || columnName == "_ctx" { + continue + } + + switch columnName { + case "original_file_name": + fields = append(fields, "originalFilename") + default: + fields = append(fields, strcase.ToLowerCamel(columnName)) + } + } + + givenFields := strings.Join(fields, ", ") + requestedFields = append(requestedFields, googleapi.Field(fmt.Sprintf("nextPageToken, files(%s)", givenFields))) + + return requestedFields +} diff --git a/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_gmail_draft.go b/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_gmail_draft.go new file mode 100644 index 00000000..a6ae6353 --- /dev/null +++ b/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_gmail_draft.go @@ -0,0 +1,231 @@ +package googleworkspace + +import ( + "context" + "fmt" + "strconv" + "strings" + + "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" + + "google.golang.org/api/gmail/v1" +) + +//// TABLE DEFINITION + +func tableGoogleWorkspaceGmailDraft(_ context.Context) *plugin.Table { + return &plugin.Table{ + Name: "googleworkspace_gmail_draft", + Description: "Retrieves draft messages in the specified user's mailbox.", + List: &plugin.ListConfig{ + Hydrate: listGmailDrafts, + KeyColumns: []*plugin.KeyColumn{ + { + Name: "user_id", + Require: plugin.Required, + }, + { + Name: "query", + Require: plugin.Optional, + }, + }, + }, + Get: &plugin.GetConfig{ + KeyColumns: plugin.AllColumns([]string{"draft_id", "user_id"}), + Hydrate: getGmailDraft, + }, + Columns: []*plugin.Column{ + { + Name: "draft_id", + Description: "The immutable ID of the draft.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Id"), + }, + { + Name: "message_id", + Description: "The immutable ID of the message.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Message.Id"), + }, + { + Name: "message_thread_id", + Description: "The ID of the thread the message belongs to.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Message.ThreadId"), + }, + { + Name: "user_id", + Description: "User's email address. If not specified, indicates the current authenticated user.", + Type: proto.ColumnType_STRING, + Transform: transform.FromQual("user_id"), + }, + { + Name: "message_history_id", + Description: "The ID of the last history record that modified this message.", + Type: proto.ColumnType_STRING, + Hydrate: getGmailDraft, + Transform: transform.FromField("Message.HistoryId"), + }, + { + Name: "message_internal_date", + Description: "The internal message creation timestamp which determines ordering in the inbox.", + Type: proto.ColumnType_TIMESTAMP, + Hydrate: getGmailDraft, + Transform: transform.FromField("Message.InternalDate").Transform(transform.UnixMsToTimestamp), + }, + { + Name: "message_raw", + Description: "The entire email message in an RFC 2822 formatted and base64url encoded string.", + Type: proto.ColumnType_STRING, + Hydrate: getGmailDraft, + Transform: transform.FromField("Message.Raw").NullIfZero(), + }, + { + Name: "message_size_estimate", + Description: "Estimated size in bytes of the message.", + Type: proto.ColumnType_INT, + Hydrate: getGmailDraft, + Transform: transform.FromField("Message.SizeEstimate"), + }, + { + Name: "message_snippet", + Description: "A short part of the message text.", + Type: proto.ColumnType_STRING, + Hydrate: getGmailDraft, + Transform: transform.FromField("Message.Snippet").NullIfZero(), + }, + { + Name: "query", + Description: "A string to filter messages matching the specified query.", + Type: proto.ColumnType_STRING, + Transform: transform.FromQual("query"), + }, + { + Name: "message_label_ids", + Description: "A list of IDs of labels applied to this message.", + Type: proto.ColumnType_JSON, + Hydrate: getGmailDraft, + Transform: transform.FromField("Message.LabelIds"), + }, + { + Name: "message_payload", + Description: "The parsed email structure in the message parts.", + Type: proto.ColumnType_JSON, + Hydrate: getGmailDraft, + Transform: transform.FromField("Message.Payload"), + }, + }, + } +} + +//// LIST FUNCTION + +func listGmailDrafts(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := GmailService(ctx, d) + if err != nil { + return nil, err + } + + var userID string + if d.EqualsQuals["user_id"] != nil { + userID = d.EqualsQuals["user_id"].GetStringValue() + } + + var queryFilter, query string + var filter []string + + if d.Quals["message_internal_date"] != nil { + for _, q := range d.Quals["message_internal_date"].Quals { + tsSecs := q.Value.GetTimestampValue().GetSeconds() + switch q.Operator { + case "=": + filter = append(filter, fmt.Sprintf("after:%s before:%s", strconv.Itoa(int(tsSecs)), strconv.Itoa(int(tsSecs+1)))) + case ">=": + filter = append(filter, fmt.Sprintf("after:%s", strconv.Itoa(int(tsSecs)))) + case ">": + filter = append(filter, fmt.Sprintf("after:%s", strconv.Itoa(int(tsSecs)))) + case "<=": + filter = append(filter, fmt.Sprintf("before:%s", strconv.Itoa(int(tsSecs)+1))) + case "<": + filter = append(filter, fmt.Sprintf("before:%s", strconv.Itoa(int(tsSecs)))) + } + } + } + + // Only return messages matching the specified query. Supports the same query format as the Gmail search box. + // For example, "from:someuser@example.com is:unread" + if d.EqualsQuals["query"] != nil { + queryFilter = d.EqualsQuals["query"].GetStringValue() + } + + if queryFilter != "" { + query = queryFilter + } else if len(filter) > 0 { + query = strings.Join(filter, " and ") + } + + // Setting the maximum number of messages, API can return in a single page + maxResults := int64(500) + + limit := d.QueryContext.Limit + if d.QueryContext.Limit != nil { + if *limit < maxResults { + maxResults = *limit + } + } + + resp := service.Users.Drafts.List(userID).Q(query).MaxResults(maxResults) + if err := resp.Pages(ctx, func(page *gmail.ListDraftsResponse) error { + for _, draft := range page.Drafts { + d.StreamListItem(ctx, draft) + + // Context can be cancelled due to manual cancellation or the limit has been hit + if plugin.IsCancelled(ctx) { + page.NextPageToken = "" + break + } + } + return nil + }); err != nil { + return nil, err + } + + return nil, nil +} + +//// HYDRATE FUNCTIONS + +func getGmailDraft(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := GmailService(ctx, d) + if err != nil { + return nil, err + } + + var userID string + if d.EqualsQuals["user_id"] != nil { + userID = d.EqualsQuals["user_id"].GetStringValue() + } + + var draftID string + if h.Item != nil { + draftID = h.Item.(*gmail.Draft).Id + } else { + draftID = d.EqualsQuals["draft_id"].GetStringValue() + } + + // Return nil, if no input provided + if draftID == "" || userID == "" { + return nil, nil + } + + resp, err := service.Users.Drafts.Get(userID, draftID).Do() + if err != nil { + return nil, err + } + + return resp, nil +} diff --git a/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_gmail_message.go b/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_gmail_message.go new file mode 100644 index 00000000..3df53416 --- /dev/null +++ b/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_gmail_message.go @@ -0,0 +1,261 @@ +package googleworkspace + +import ( + "context" + "fmt" + "regexp" + "strconv" + "strings" + + "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" + + "google.golang.org/api/gmail/v1" +) + +//// TABLE DEFINITION + +func tableGoogleWorkspaceGmailMessage(_ context.Context) *plugin.Table { + return &plugin.Table{ + Name: "googleworkspace_gmail_message", + Description: "Retrieves messages in the specified user's mailbox.", + List: &plugin.ListConfig{ + Hydrate: listGmailMessages, + KeyColumns: []*plugin.KeyColumn{ + { + Name: "user_id", + Require: plugin.Required, + }, + { + Name: "sender_email", + Require: plugin.Optional, + }, + { + Name: "internal_date", + Require: plugin.Optional, + Operators: []string{">", ">=", "=", "<", "<="}, + }, + { + Name: "query", + Require: plugin.Optional, + }, + }, + }, + Get: &plugin.GetConfig{ + KeyColumns: plugin.AllColumns([]string{"id", "user_id"}), + Hydrate: getGmailMessage, + MaxConcurrency: 50, + }, + Columns: []*plugin.Column{ + { + Name: "id", + Description: "The immutable ID of the message.", + Type: proto.ColumnType_STRING, + }, + { + Name: "thread_id", + Description: "The ID of the thread the message belongs to.", + Type: proto.ColumnType_STRING, + }, + { + Name: "user_id", + Description: "User's email address. If not specified, indicates the current authenticated user.", + Type: proto.ColumnType_STRING, + Transform: transform.FromQual("user_id"), + }, + { + Name: "history_id", + Description: "The ID of the last history record that modified this message.", + Type: proto.ColumnType_STRING, + Hydrate: getGmailMessage, + }, + { + Name: "sender_email", + Description: "Specifies the email address of the sender.", + Type: proto.ColumnType_STRING, + Hydrate: getGmailMessage, + Transform: transform.From(extractMessageSender), + }, + { + Name: "internal_date", + Description: "The internal message creation timestamp which determines ordering in the inbox.", + Type: proto.ColumnType_TIMESTAMP, + Hydrate: getGmailMessage, + Transform: transform.FromField("InternalDate").Transform(transform.UnixMsToTimestamp), + }, + { + Name: "raw", + Description: "The entire email message in an RFC 2822 formatted and base64url encoded string.", + Type: proto.ColumnType_STRING, + Hydrate: getGmailMessage, + }, + { + Name: "size_estimate", + Description: "Estimated size in bytes of the message.", + Type: proto.ColumnType_INT, + Hydrate: getGmailMessage, + }, + { + Name: "snippet", + Description: "A short part of the message text.", + Type: proto.ColumnType_STRING, + Hydrate: getGmailMessage, + }, + { + Name: "query", + Description: "A string to filter messages matching the specified query.", + Type: proto.ColumnType_STRING, + Transform: transform.FromQual("query"), + }, + { + Name: "label_ids", + Description: "A list of IDs of labels applied to this message.", + Type: proto.ColumnType_JSON, + Hydrate: getGmailMessage, + }, + { + Name: "payload", + Description: "The parsed email structure in the message parts.", + Type: proto.ColumnType_JSON, + Hydrate: getGmailMessage, + }, + }, + } +} + +//// LIST FUNCTION + +func listGmailMessages(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := GmailService(ctx, d) + if err != nil { + return nil, err + } + + var userID string + if d.EqualsQuals["user_id"] != nil { + userID = d.EqualsQuals["user_id"].GetStringValue() + } + + var queryFilter, query string + var filter []string + + if d.EqualsQuals["sender_email"] != nil { + filter = append(filter, fmt.Sprintf("%s = \"%s\"", "from", d.EqualsQuals["sender_email"].GetStringValue())) + } + + if d.Quals["internal_date"] != nil { + for _, q := range d.Quals["internal_date"].Quals { + tsSecs := q.Value.GetTimestampValue().GetSeconds() + switch q.Operator { + case "=": + filter = append(filter, fmt.Sprintf("after:%s before:%s", strconv.Itoa(int(tsSecs)), strconv.Itoa(int(tsSecs+1)))) + case ">=": + filter = append(filter, fmt.Sprintf("after:%s", strconv.Itoa(int(tsSecs)))) + case ">": + filter = append(filter, fmt.Sprintf("after:%s", strconv.Itoa(int(tsSecs)))) + case "<=": + filter = append(filter, fmt.Sprintf("before:%s", strconv.Itoa(int(tsSecs)+1))) + case "<": + filter = append(filter, fmt.Sprintf("before:%s", strconv.Itoa(int(tsSecs)))) + } + } + } + + // Only return messages matching the specified query. Supports the same query format as the Gmail search box. + // For example, "from:someuser@example.com is:unread" + // Note: Parameter cannot be used when accessing the api using the gmail.metadata scope. + if d.EqualsQuals["query"] != nil { + queryFilter = d.EqualsQuals["query"].GetStringValue() + } + + if queryFilter != "" { + query = queryFilter + } else if len(filter) > 0 { + query = strings.Join(filter, " and ") + } + + // Setting the maximum number of messages, API can return in a single page + maxResults := int64(500) + + limit := d.QueryContext.Limit + if d.QueryContext.Limit != nil { + if *limit < maxResults { + maxResults = *limit + } + } + + resp := service.Users.Messages.List(userID).Q(query).MaxResults(maxResults) + if err := resp.Pages(ctx, func(page *gmail.ListMessagesResponse) error { + for _, message := range page.Messages { + d.StreamListItem(ctx, message) + + // Context can be cancelled due to manual cancellation or the limit has been hit + if plugin.IsCancelled(ctx) { + page.NextPageToken = "" + break + } + } + return nil + }); err != nil { + return nil, err + } + + return nil, nil +} + +//// HYDRATE FUNCTIONS + +func getGmailMessage(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := GmailService(ctx, d) + if err != nil { + return nil, err + } + + var userID string + if d.EqualsQuals["user_id"] != nil { + userID = d.EqualsQuals["user_id"].GetStringValue() + } + + var messageID string + if h.Item != nil { + messageID = h.Item.(*gmail.Message).Id + } else { + messageID = d.EqualsQuals["id"].GetStringValue() + } + + // Return nil, if no input provided + if messageID == "" || userID == "" { + return nil, nil + } + + resp, err := service.Users.Messages.Get(userID, messageID).Do() + if err != nil { + return nil, err + } + + return resp, nil +} + +//// TRANSFORM FUNCTIONS + +func extractMessageSender(ctx context.Context, d *transform.TransformData) (interface{}, error) { + data := d.HydrateItem.(*gmail.Message) + if data.Payload == nil { + return nil, nil + } + + for _, payloadHeader := range data.Payload.Headers { + if payloadHeader.Name == "From" { + regexExp := regexp.MustCompile(`\<(.*?) *\>`) + senderEmail := regexExp.FindStringSubmatch(payloadHeader.Value) + if len(senderEmail) > 1 { + return senderEmail[1], nil + } + } + } + + return nil, nil +} diff --git a/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_gmail_my_draft.go b/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_gmail_my_draft.go new file mode 100644 index 00000000..6c881676 --- /dev/null +++ b/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_gmail_my_draft.go @@ -0,0 +1,216 @@ +package googleworkspace + +import ( + "context" + "fmt" + "strconv" + "strings" + + "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" + + "google.golang.org/api/gmail/v1" +) + +//// TABLE DEFINITION + +func tableGoogleWorkspaceGmailMyDraft(_ context.Context) *plugin.Table { + return &plugin.Table{ + Name: "googleworkspace_gmail_my_draft", + Description: "Retrieves draft messages in the current authenticated user's mailbox.", + List: &plugin.ListConfig{ + Hydrate: listGmailMyDrafts, + KeyColumns: []*plugin.KeyColumn{ + { + Name: "message_internal_date", + Require: plugin.Optional, + Operators: []string{">", ">=", "=", "<", "<="}, + }, + { + Name: "query", + Require: plugin.Optional, + }, + }, + }, + Get: &plugin.GetConfig{ + KeyColumns: plugin.SingleColumn("draft_id"), + Hydrate: getGmailMyDraft, + }, + Columns: []*plugin.Column{ + { + Name: "draft_id", + Description: "The immutable ID of the draft.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Id"), + }, + { + Name: "message_id", + Description: "The immutable ID of the message.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Message.Id"), + }, + { + Name: "message_thread_id", + Description: "The ID of the thread the message belongs to.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Message.ThreadId"), + }, + { + Name: "message_history_id", + Description: "The ID of the last history record that modified this message.", + Type: proto.ColumnType_STRING, + Hydrate: getGmailMyDraft, + Transform: transform.FromField("Message.HistoryId"), + }, + { + Name: "message_internal_date", + Description: "The internal message creation timestamp which determines ordering in the inbox.", + Type: proto.ColumnType_TIMESTAMP, + Hydrate: getGmailMyDraft, + Transform: transform.FromField("Message.InternalDate").Transform(transform.UnixMsToTimestamp), + }, + { + Name: "message_raw", + Description: "The entire email message in an RFC 2822 formatted and base64url encoded string.", + Type: proto.ColumnType_STRING, + Hydrate: getGmailMyDraft, + Transform: transform.FromField("Message.Raw").NullIfZero(), + }, + { + Name: "message_size_estimate", + Description: "Estimated size in bytes of the message.", + Type: proto.ColumnType_INT, + Hydrate: getGmailMyDraft, + Transform: transform.FromField("Message.SizeEstimate"), + }, + { + Name: "message_snippet", + Description: "A short part of the message text.", + Type: proto.ColumnType_STRING, + Hydrate: getGmailMyDraft, + Transform: transform.FromField("Message.Snippet").NullIfZero(), + }, + { + Name: "query", + Description: "A string to filter messages matching the specified query.", + Type: proto.ColumnType_STRING, + Transform: transform.FromQual("query"), + }, + { + Name: "message_label_ids", + Description: "A list of IDs of labels applied to this message.", + Type: proto.ColumnType_JSON, + Hydrate: getGmailMyDraft, + Transform: transform.FromField("Message.LabelIds"), + }, + { + Name: "message_payload", + Description: "The parsed email structure in the message parts.", + Type: proto.ColumnType_JSON, + Hydrate: getGmailMyDraft, + Transform: transform.FromField("Message.Payload"), + }, + }, + } +} + +//// LIST FUNCTION + +func listGmailMyDrafts(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := GmailService(ctx, d) + if err != nil { + return nil, err + } + + var queryFilter, query string + var filter []string + + if d.Quals["message_internal_date"] != nil { + for _, q := range d.Quals["message_internal_date"].Quals { + tsSecs := q.Value.GetTimestampValue().GetSeconds() + switch q.Operator { + case "=": + filter = append(filter, fmt.Sprintf("after:%s before:%s", strconv.Itoa(int(tsSecs)), strconv.Itoa(int(tsSecs+1)))) + case ">=": + filter = append(filter, fmt.Sprintf("after:%s", strconv.Itoa(int(tsSecs)))) + case ">": + filter = append(filter, fmt.Sprintf("after:%s", strconv.Itoa(int(tsSecs)))) + case "<=": + filter = append(filter, fmt.Sprintf("before:%s", strconv.Itoa(int(tsSecs)+1))) + case "<": + filter = append(filter, fmt.Sprintf("before:%s", strconv.Itoa(int(tsSecs)))) + } + } + } + + // Only return messages matching the specified query. Supports the same query format as the Gmail search box. + // For example, "from:someuser@example.com is:unread" + if d.EqualsQuals["query"] != nil { + queryFilter = d.EqualsQuals["query"].GetStringValue() + } + + if queryFilter != "" { + query = queryFilter + } else if len(filter) > 0 { + query = strings.Join(filter, " and ") + } + + // Setting the maximum number of messages, API can return in a single page + maxResults := int64(500) + + limit := d.QueryContext.Limit + if d.QueryContext.Limit != nil { + if *limit < maxResults { + maxResults = *limit + } + } + + resp := service.Users.Drafts.List("me").Q(query).MaxResults(maxResults) + if err := resp.Pages(ctx, func(page *gmail.ListDraftsResponse) error { + for _, draft := range page.Drafts { + d.StreamListItem(ctx, draft) + + // Context can be cancelled due to manual cancellation or the limit has been hit + if plugin.IsCancelled(ctx) { + page.NextPageToken = "" + break + } + } + return nil + }); err != nil { + return nil, err + } + + return nil, nil +} + +//// HYDRATE FUNCTIONS + +func getGmailMyDraft(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := GmailService(ctx, d) + if err != nil { + return nil, err + } + + var draftID string + if h.Item != nil { + draftID = h.Item.(*gmail.Draft).Id + } else { + draftID = d.EqualsQuals["draft_id"].GetStringValue() + } + + // Return nil, if no input provided + if draftID == "" { + return nil, nil + } + + resp, err := service.Users.Drafts.Get("me", draftID).Do() + if err != nil { + return nil, err + } + + return resp, nil +} diff --git a/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_gmail_my_message.go b/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_gmail_my_message.go new file mode 100644 index 00000000..a3a4dc71 --- /dev/null +++ b/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_gmail_my_message.go @@ -0,0 +1,219 @@ +package googleworkspace + +import ( + "context" + "fmt" + "strconv" + "strings" + + "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" + + "google.golang.org/api/gmail/v1" +) + +//// TABLE DEFINITION + +func tableGoogleWorkspaceGmailMyMessage(_ context.Context) *plugin.Table { + return &plugin.Table{ + Name: "googleworkspace_gmail_my_message", + Description: "Retrieves messages in the current authenticated user's mailbox.", + List: &plugin.ListConfig{ + Hydrate: listGmailMyMessages, + KeyColumns: []*plugin.KeyColumn{ + { + Name: "sender_email", + Require: plugin.Optional, + }, + { + Name: "internal_date", + Require: plugin.Optional, + Operators: []string{">", ">=", "=", "<", "<="}, + }, + { + Name: "query", + Require: plugin.Optional, + }, + }, + }, + Get: &plugin.GetConfig{ + KeyColumns: plugin.SingleColumn("id"), + Hydrate: getGmailMyMessage, + MaxConcurrency: 50, + }, + Columns: []*plugin.Column{ + { + Name: "id", + Description: "The immutable ID of the message.", + Type: proto.ColumnType_STRING, + }, + { + Name: "thread_id", + Description: "The ID of the thread the message belongs to.", + Type: proto.ColumnType_STRING, + }, + { + Name: "history_id", + Description: "The ID of the last history record that modified this message.", + Type: proto.ColumnType_STRING, + Hydrate: getGmailMyMessage, + }, + { + Name: "sender_email", + Description: "Specifies the email address of the sender.", + Type: proto.ColumnType_STRING, + Hydrate: getGmailMyMessage, + Transform: transform.From(extractMessageSender), + }, + { + Name: "internal_date", + Description: "The internal message creation timestamp which determines ordering in the inbox.", + Type: proto.ColumnType_TIMESTAMP, + Hydrate: getGmailMyMessage, + Transform: transform.FromField("InternalDate").Transform(transform.UnixMsToTimestamp), + }, + { + Name: "raw", + Description: "The entire email message in an RFC 2822 formatted and base64url encoded string.", + Type: proto.ColumnType_STRING, + Hydrate: getGmailMyMessage, + }, + { + Name: "size_estimate", + Description: "Estimated size in bytes of the message.", + Type: proto.ColumnType_INT, + Hydrate: getGmailMyMessage, + }, + { + Name: "snippet", + Description: "A short part of the message text.", + Type: proto.ColumnType_STRING, + Hydrate: getGmailMyMessage, + }, + { + Name: "query", + Description: "A string to filter messages matching the specified query.", + Type: proto.ColumnType_STRING, + Transform: transform.FromQual("query"), + }, + { + Name: "label_ids", + Description: "A list of IDs of labels applied to this message.", + Type: proto.ColumnType_JSON, + Hydrate: getGmailMyMessage, + }, + { + Name: "payload", + Description: "The parsed email structure in the message parts.", + Type: proto.ColumnType_JSON, + Hydrate: getGmailMyMessage, + }, + }, + } +} + +//// LIST FUNCTION + +func listGmailMyMessages(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := GmailService(ctx, d) + if err != nil { + return nil, err + } + + var queryFilter, query string + var filter []string + + if d.EqualsQuals["sender_email"] != nil { + filter = append(filter, fmt.Sprintf("%s = \"%s\"", "from", d.EqualsQuals["sender_email"].GetStringValue())) + } + + if d.Quals["internal_date"] != nil { + for _, q := range d.Quals["internal_date"].Quals { + tsSecs := q.Value.GetTimestampValue().GetSeconds() + switch q.Operator { + case "=": + filter = append(filter, fmt.Sprintf("after:%s before:%s", strconv.Itoa(int(tsSecs)), strconv.Itoa(int(tsSecs+1)))) + case ">=": + filter = append(filter, fmt.Sprintf("after:%s", strconv.Itoa(int(tsSecs)))) + case ">": + filter = append(filter, fmt.Sprintf("after:%s", strconv.Itoa(int(tsSecs)))) + case "<=": + filter = append(filter, fmt.Sprintf("before:%s", strconv.Itoa(int(tsSecs)+1))) + case "<": + filter = append(filter, fmt.Sprintf("before:%s", strconv.Itoa(int(tsSecs)))) + } + } + } + + // Only return messages matching the specified query. Supports the same query format as the Gmail search box. + // For example, "from:someuser@example.com is:unread" + // Note: Parameter cannot be used when accessing the api using the gmail.metadata scope. + if d.EqualsQuals["query"] != nil { + queryFilter = d.EqualsQuals["query"].GetStringValue() + } + + if queryFilter != "" { + query = queryFilter + } else if len(filter) > 0 { + query = strings.Join(filter, " and ") + } + + // Setting the maximum number of messages, API can return in a single page + maxResults := int64(500) + + limit := d.QueryContext.Limit + if d.QueryContext.Limit != nil { + if *limit < maxResults { + maxResults = *limit + } + } + + resp := service.Users.Messages.List("me").Q(query).MaxResults(maxResults) + if err := resp.Pages(ctx, func(page *gmail.ListMessagesResponse) error { + for _, message := range page.Messages { + d.StreamListItem(ctx, message) + + // Context can be cancelled due to manual cancellation or the limit has been hit + if plugin.IsCancelled(ctx) { + page.NextPageToken = "" + break + } + } + return nil + }); err != nil { + return nil, err + } + + return nil, nil +} + +//// HYDRATE FUNCTIONS + +func getGmailMyMessage(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := GmailService(ctx, d) + if err != nil { + return nil, err + } + + var messageID string + if h.Item != nil { + messageID = h.Item.(*gmail.Message).Id + } else { + messageID = d.EqualsQuals["id"].GetStringValue() + } + + // Return nil, if no input provided + if messageID == "" { + return nil, nil + } + + resp, err := service.Users.Messages.Get("me", messageID).Do() + if err != nil { + return nil, err + } + + return resp, nil +} diff --git a/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_gmail_my_settings.go b/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_gmail_my_settings.go new file mode 100644 index 00000000..15b15cfe --- /dev/null +++ b/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_gmail_my_settings.go @@ -0,0 +1,227 @@ +package googleworkspace + +import ( + "context" + + "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" + "google.golang.org/api/googleapi" +) + +//// TABLE DEFINITION + +func tableGoogleWorkspaceGmailMySettings(_ context.Context) *plugin.Table { + return &plugin.Table{ + Name: "googleworkspace_gmail_my_settings", + Description: "Retrieves settings for the current authenticated user account.", + List: &plugin.ListConfig{ + Hydrate: listGmailMyUser, + }, + Columns: []*plugin.Column{ + { + Name: "user_email", + Description: "The user's email address.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("EmailAddress"), + }, + { + Name: "display_language", + Description: "Specifies the language settings for the specified account.", + Type: proto.ColumnType_STRING, + Hydrate: getGmailMyLanguage, + }, + { + Name: "auto_forwarding", + Description: "Describes the auto-forwarding setting for the specified account.", + Type: proto.ColumnType_JSON, + Hydrate: getGmailMyAutoForwardingSetting, + Transform: transform.FromValue(), + }, + { + Name: "delegates", + Description: "A list of delegates for the specified account.", + Type: proto.ColumnType_JSON, + Hydrate: listGmailMyDelegateSettings, + Transform: transform.FromValue(), + }, + { + Name: "imap", + Description: "Describes the IMAP setting for the specified account.", + Type: proto.ColumnType_JSON, + Hydrate: getGmailMyImapSetting, + Transform: transform.FromValue(), + }, + { + Name: "pop", + Description: "Describes the POP settings for the specified account.", + Type: proto.ColumnType_JSON, + Hydrate: getGmailMyPopSetting, + Transform: transform.FromValue(), + }, + { + Name: "vacation", + Description: "Describes the vacation responder settings for the specified account.", + Type: proto.ColumnType_JSON, + Hydrate: getGmailMyVacationSetting, + Transform: transform.FromValue(), + }, + }, + } +} + +//// LIST FUNCTION + +func listGmailMyUser(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := GmailService(ctx, d) + if err != nil { + return nil, err + } + + resp, err := service.Users.GetProfile("me").Do() + if err != nil { + return nil, err + } + d.StreamListItem(ctx, resp) + + return nil, nil +} + +//// HYDRATE FUNCTIONS + +// Lists the delegates for the current authenticated user's account. +// Note: This method is only available to service account clients that have been delegated domain-wide authority. +func listGmailMyDelegateSettings(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := GmailService(ctx, d) + if err != nil { + return nil, err + } + + resp, err := service.Users.Settings.Delegates.List("me").Do() + if err != nil { + if gerr, ok := err.(*googleapi.Error); ok { + // Since this method is only available to service account clients that have been delegated domain-wide authority, + // return nil if using the OAuth 2.0 client auth + if gerr.Code == 403 && gerr.Message == "Access restricted to service accounts that have been delegated domain-wide authority" { + return nil, nil + } + } + return nil, err + } + + return resp.Delegates, nil +} + +// Gets the auto-forwarding setting for the current authenticated user's account. +func getGmailMyAutoForwardingSetting(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := GmailService(ctx, d) + if err != nil { + return nil, err + } + + resp, err := service.Users.Settings.GetAutoForwarding("me").Do() + if err != nil { + return nil, err + } + + // If the property is set with default value, it doesn't show in response + if resp != nil { + result := map[string]interface{}{ + "disposition": resp.Disposition, + "emailAddress": resp.EmailAddress, + "enabled": resp.Enabled, + } + return result, nil + } + + return nil, nil +} + +// Gets IMAP settings. +func getGmailMyImapSetting(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := GmailService(ctx, d) + if err != nil { + return nil, err + } + + resp, err := service.Users.Settings.GetImap("me").Do() + if err != nil { + return nil, err + } + + // If the property is set with default value, it doesn't show in response + if resp != nil { + result := map[string]interface{}{ + "autoExpunge": resp.AutoExpunge, + "enabled": resp.Enabled, + "expungeBehavior": resp.ExpungeBehavior, + "maxFolderSize": resp.MaxFolderSize, + } + return result, nil + } + + return nil, nil +} + +// Gets language settings. +func getGmailMyLanguage(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := GmailService(ctx, d) + if err != nil { + return nil, err + } + + resp, err := service.Users.Settings.GetLanguage("me").Do() + if err != nil { + return nil, err + } + + return resp, nil +} + +// Gets POP settings. +func getGmailMyPopSetting(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := GmailService(ctx, d) + if err != nil { + return nil, err + } + + resp, err := service.Users.Settings.GetPop("me").Do() + if err != nil { + return nil, err + } + + return resp, nil +} + +// Gets vacation responder settings. +func getGmailMyVacationSetting(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := GmailService(ctx, d) + if err != nil { + return nil, err + } + + resp, err := service.Users.Settings.GetVacation("me").Do() + if err != nil { + return nil, err + } + + // If the property is set with default value, it doesn't show in response + if resp != nil { + result := map[string]interface{}{ + "enableAutoReply": resp.EnableAutoReply, + "responseSubject": resp.ResponseSubject, + "restrictToContacts": resp.RestrictToContacts, + "restrictToDomain": resp.RestrictToDomain, + } + return result, nil + } + + return nil, nil +} diff --git a/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_gmail_settings.go b/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_gmail_settings.go new file mode 100644 index 00000000..972157a2 --- /dev/null +++ b/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_gmail_settings.go @@ -0,0 +1,241 @@ +package googleworkspace + +import ( + "context" + + "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" + + "google.golang.org/api/gmail/v1" + "google.golang.org/api/googleapi" +) + +//// TABLE DEFINITION + +func tableGoogleWorkspaceGmailSettings(_ context.Context) *plugin.Table { + return &plugin.Table{ + Name: "googleworkspace_gmail_settings", + Description: "Retrieves settings for the specified account.", + List: &plugin.ListConfig{ + Hydrate: listGmailUsers, + KeyColumns: plugin.SingleColumn("user_email"), + }, + Columns: []*plugin.Column{ + { + Name: "user_email", + Description: "The specified user's email address.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("EmailAddress"), + }, + { + Name: "display_language", + Description: "Specifies the language settings for the specified account.", + Type: proto.ColumnType_STRING, + Hydrate: getGmailLanguage, + }, + { + Name: "auto_forwarding", + Description: "Describes the auto-forwarding setting for the specified account.", + Type: proto.ColumnType_JSON, + Hydrate: getGmailSettingAutoForwarding, + Transform: transform.FromValue(), + }, + { + Name: "delegates", + Description: "A list of delegates for the specified account.", + Type: proto.ColumnType_JSON, + Hydrate: listGmailDelegateSettings, + Transform: transform.FromValue(), + }, + { + Name: "imap", + Description: "Describes the IMAP setting for the specified account.", + Type: proto.ColumnType_JSON, + Hydrate: getGmailSettingImap, + Transform: transform.FromValue(), + }, + { + Name: "pop", + Description: "Describes the POP settings for the specified account.", + Type: proto.ColumnType_JSON, + Hydrate: getGmailPopSetting, + Transform: transform.FromValue(), + }, + { + Name: "vacation", + Description: "Describes the vacation responder settings for the specified account.", + Type: proto.ColumnType_JSON, + Hydrate: getGmailVacationSetting, + Transform: transform.FromValue(), + }, + }, + } +} + +//// LIST FUNCTION + +func listGmailUsers(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := GmailService(ctx, d) + if err != nil { + return nil, err + } + + var userID string + if d.EqualsQuals["user_email"] != nil { + userID = d.EqualsQuals["user_email"].GetStringValue() + } + + resp, err := service.Users.GetProfile(userID).Do() + if err != nil { + return nil, err + } + d.StreamListItem(ctx, resp) + + return nil, nil +} + +//// HYDRATE FUNCTIONS + +// Lists the delegates for the specified account. +// Note: This method is only available to service account clients that have been delegated domain-wide authority. +func listGmailDelegateSettings(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := GmailService(ctx, d) + if err != nil { + return nil, err + } + userID := h.Item.(*gmail.Profile).EmailAddress + + resp, err := service.Users.Settings.Delegates.List(userID).Do() + if err != nil { + if gerr, ok := err.(*googleapi.Error); ok { + // Since this method is only available to service account clients that have been delegated domain-wide authority, + // return nil if using the OAuth 2.0 client auth + if gerr.Code == 403 && gerr.Message == "Access restricted to service accounts that have been delegated domain-wide authority" { + return nil, nil + } + } + return nil, err + } + + return resp.Delegates, nil +} + +// Gets the auto-forwarding setting for the specified account. +func getGmailSettingAutoForwarding(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := GmailService(ctx, d) + if err != nil { + return nil, err + } + userID := h.Item.(*gmail.Profile).EmailAddress + + resp, err := service.Users.Settings.GetAutoForwarding(userID).Do() + if err != nil { + return nil, err + } + + // If the property is set with default value, it doesn't show in response + if resp != nil { + result := map[string]interface{}{ + "disposition": resp.Disposition, + "emailAddress": resp.EmailAddress, + "enabled": resp.Enabled, + } + return result, nil + } + + return nil, nil +} + +// Gets IMAP settings. +func getGmailSettingImap(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := GmailService(ctx, d) + if err != nil { + return nil, err + } + userID := h.Item.(*gmail.Profile).EmailAddress + + resp, err := service.Users.Settings.GetImap(userID).Do() + if err != nil { + return nil, err + } + + // If the property is set with default value, it doesn't show in response + if resp != nil { + result := map[string]interface{}{ + "autoExpunge": resp.AutoExpunge, + "enabled": resp.Enabled, + "expungeBehavior": resp.ExpungeBehavior, + "maxFolderSize": resp.MaxFolderSize, + } + return result, nil + } + + return nil, nil +} + +// Gets language settings. +func getGmailLanguage(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := GmailService(ctx, d) + if err != nil { + return nil, err + } + userID := h.Item.(*gmail.Profile).EmailAddress + + resp, err := service.Users.Settings.GetLanguage(userID).Do() + if err != nil { + return nil, err + } + + return resp, nil +} + +// Gets POP settings. +func getGmailPopSetting(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := GmailService(ctx, d) + if err != nil { + return nil, err + } + userID := h.Item.(*gmail.Profile).EmailAddress + + resp, err := service.Users.Settings.GetPop(userID).Do() + if err != nil { + return nil, err + } + + return resp, nil +} + +// Gets vacation responder settings. +func getGmailVacationSetting(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := GmailService(ctx, d) + if err != nil { + return nil, err + } + userID := h.Item.(*gmail.Profile).EmailAddress + + resp, err := service.Users.Settings.GetVacation(userID).Do() + if err != nil { + return nil, err + } + + // If the property is set with default value, it doesn't show in response + if resp != nil { + result := map[string]interface{}{ + "enableAutoReply": resp.EnableAutoReply, + "responseSubject": resp.ResponseSubject, + "restrictToContacts": resp.RestrictToContacts, + "restrictToDomain": resp.RestrictToDomain, + } + return result, nil + } + + return nil, nil +} diff --git a/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_people_contact.go b/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_people_contact.go new file mode 100644 index 00000000..b7578813 --- /dev/null +++ b/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_people_contact.go @@ -0,0 +1,237 @@ +package googleworkspace + +import ( + "context" + + "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" + + "google.golang.org/api/people/v1" +) + +func peopleContacts() []*plugin.Column { + return []*plugin.Column{ + { + Name: "resource_name", + Description: "The resource name for the contact group, assigned by the server.", + Type: proto.ColumnType_STRING, + }, + { + Name: "display_name", + Description: "The display name formatted according to the locale specified by the viewer's account.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Name.DisplayName").NullIfZero(), + }, + { + Name: "given_name", + Description: "The given name of the user contact.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Name.GivenName").NullIfZero(), + }, + { + Name: "primary_email_address", + Description: "The primary email address of the user contact.", + Type: proto.ColumnType_STRING, + Transform: transform.From(extractPrimaryEmailAddress), + }, + { + Name: "gender", + Description: "The gender for the person.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Gender.Value").NullIfZero(), + }, + { + Name: "birthday", + Description: "The date of the birthday.", + Type: proto.ColumnType_JSON, + Transform: transform.FromField("Birthday.Date").NullIfZero(), + }, + { + Name: "email_addresses", + Description: "The person's email addresses.", + Type: proto.ColumnType_JSON, + }, + { + Name: "addresses", + Description: "The person's street addresses.", + Type: proto.ColumnType_JSON, + }, + { + Name: "biography", + Description: "The person's biography.", + Type: proto.ColumnType_JSON, + }, + { + Name: "calendar_urls", + Description: "The person's calendar URLs.", + Type: proto.ColumnType_JSON, + }, + { + Name: "client_data", + Description: "The person's client data.", + Type: proto.ColumnType_JSON, + }, + { + Name: "cover_photos", + Description: "The person's cover photos.", + Type: proto.ColumnType_JSON, + }, + { + Name: "events", + Description: "The person's events.", + Type: proto.ColumnType_JSON, + }, + { + Name: "external_ids", + Description: "The person's external IDs.", + Type: proto.ColumnType_JSON, + }, + { + Name: "interests", + Description: "The person's interests.", + Type: proto.ColumnType_JSON, + }, + { + Name: "locations", + Description: "The person's locations.", + Type: proto.ColumnType_JSON, + }, + { + Name: "memberships", + Description: "The person's group memberships.", + Type: proto.ColumnType_JSON, + }, + { + Name: "metadata", + Description: "Metadata about the person.", + Type: proto.ColumnType_JSON, + }, + { + Name: "nicknames", + Description: "The person's nicknames.", + Type: proto.ColumnType_JSON, + }, + { + Name: "occupations", + Description: "The person's occupations.", + Type: proto.ColumnType_JSON, + }, + { + Name: "organizations", + Description: "The person's past or current organizations.", + Type: proto.ColumnType_JSON, + }, + { + Name: "phone_numbers", + Description: "The person's phone numbers.", + Type: proto.ColumnType_JSON, + }, + { + Name: "photos", + Description: "The person's photos.", + Type: proto.ColumnType_JSON, + }, + } +} + +//// TABLE DEFINITION + +func tableGoogleWorkspacePeopleContact(_ context.Context) *plugin.Table { + return &plugin.Table{ + Name: "googleworkspace_people_contact", + Description: "Contacts owned by the authenticated user.", + List: &plugin.ListConfig{ + Hydrate: listPeopleContacts, + ShouldIgnoreError: isNotFoundError([]string{"404"}), + }, + Columns: peopleContacts(), + } +} + +type contacts = struct { + Name people.Name + Birthday people.Birthday + Gender people.Gender + Biography people.Biography + people.Person +} + +//// LIST FUNCTION + +func listPeopleContacts(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := PeopleService(ctx, d) + if err != nil { + return nil, err + } + + // Define fields the API should return + personFields := "addresses,biographies,birthdays,calendarUrls,clientData,coverPhotos,emailAddresses,events,externalIds,genders,interests,locations,memberships,metadata,miscKeywords,names,nicknames,occupations,organizations,phoneNumbers,photos,relations,sipAddresses,skills,urls,userDefined" + + // By default, API can return maximum 1000 records in a single page + maxResult := int64(1000) + + limit := d.QueryContext.Limit + if d.QueryContext.Limit != nil { + if *limit < maxResult { + maxResult = *limit + } + } + + resp := service.People.Connections.List("people/me").PersonFields(personFields).PageSize(maxResult) + if err := resp.Pages(ctx, func(page *people.ListConnectionsResponse) error { + for _, connection := range page.Connections { + // Since, 'names', 'birthdays', 'genders' and 'biographies' are singleton fields + var conn contacts + if connection.Names != nil { + conn.Name = *connection.Names[0] + } + if connection.Birthdays != nil { + conn.Birthday = *connection.Birthdays[0] + } + if connection.Genders != nil { + conn.Gender = *connection.Genders[0] + } + if connection.Biographies != nil { + conn.Biography = *connection.Biographies[0] + } + d.StreamListItem( + ctx, + contacts{ + conn.Name, + conn.Birthday, + conn.Gender, + conn.Biography, + *connection, + }) + + // Context can be cancelled due to manual cancellation or the limit has been hit + if plugin.IsCancelled(ctx) { + page.NextPageToken = "" + break + } + } + return nil + }); err != nil { + return nil, err + } + + return nil, nil +} + +//// TRANSFORM FUNCTIONS + +func extractPrimaryEmailAddress(_ context.Context, d *transform.TransformData) (interface{}, error) { + data := d.HydrateItem.(contacts) + + emailAddresses := data.EmailAddresses + + for _, email := range emailAddresses { + if email.Metadata != nil && email.Metadata.Primary { + return email.Value, nil + } + } + + return nil, nil +} diff --git a/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_people_contact_group.go b/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_people_contact_group.go new file mode 100644 index 00000000..fabaae94 --- /dev/null +++ b/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_people_contact_group.go @@ -0,0 +1,150 @@ +package googleworkspace + +import ( + "context" + + "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" + + "google.golang.org/api/people/v1" +) + +//// TABLE DEFINITION + +func tableGoogleWorkspacePeopleContactGroup(_ context.Context) *plugin.Table { + return &plugin.Table{ + Name: "googleworkspace_people_contact_group", + Description: "Contact groups owned by the authenticated user", + List: &plugin.ListConfig{ + Hydrate: listPeopleContactGroups, + KeyColumns: []*plugin.KeyColumn{ + { + Name: "max_members", + Require: plugin.Optional, + }, + }, + ShouldIgnoreError: isNotFoundError([]string{"404"}), + }, + Columns: []*plugin.Column{ + { + Name: "resource_name", + Description: "The resource name for the contact group, assigned by the server.", + Type: proto.ColumnType_STRING, + }, + { + Name: "name", + Description: "The contact group name set by the group owner or a system provided name for system groups.", + Type: proto.ColumnType_STRING, + }, + { + Name: "group_type", + Description: "The contact group type.", + Type: proto.ColumnType_STRING, + }, + { + Name: "formatted_name", + Description: "The name translated and formatted in the viewer's account locale or the `Accept-Language` HTTP header locale for system groups names.", + Type: proto.ColumnType_STRING, + }, + { + Name: "deleted", + Description: "Indicates whether the contact group resource has been deleted, or not.", + Type: proto.ColumnType_BOOL, + Transform: transform.FromField("Metadata.Deleted"), + }, + { + Name: "max_members", + Description: "Specifies the maximum number of members to return. Default is 2500, if no value provided.", + Type: proto.ColumnType_INT, + Transform: transform.FromQual("max_members"), + }, + { + Name: "member_count", + Description: "The total number of contacts in the group irrespective of max members in specified in the request.", + Type: proto.ColumnType_INT, + Default: 0, + }, + { + Name: "updated_time", + Description: "The time the group was last updated.", + Type: proto.ColumnType_TIMESTAMP, + Transform: transform.FromField("Metadata.UpdateTime"), + }, + { + Name: "client_data", + Description: "The group's client data.", + Type: proto.ColumnType_JSON, + }, + { + Name: "member_resource_names", + Description: "A list of contact person resource names that are members of the contact group.", + Type: proto.ColumnType_JSON, + }, + }, + } +} + +//// LIST FUNCTION + +func listPeopleContactGroups(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := PeopleService(ctx, d) + if err != nil { + return nil, err + } + + // Set default to 2500 + maxMembers := int64(2500) + if d.EqualsQuals["max_members"] != nil { + maxMembers = d.EqualsQuals["max_members"].GetInt64Value() + } + + // `contactGroups.batchGet` can accept maximum of 200 resource names at a time, so make sure + // `contactGroups.list` returns the same and append to this in chunks not more then 200. + pageLimit := int64(200) + + limit := d.QueryContext.Limit + if d.QueryContext.Limit != nil { + if *limit < pageLimit { + pageLimit = *limit + } + } + + var contactGroupNames [][]string + resp := service.ContactGroups.List().PageSize(pageLimit) + if err := resp.Pages(ctx, func(page *people.ListContactGroupsResponse) error { + var resourceNames []string + // create a chunk of resourceNames of size 200 + for _, contactGroup := range page.ContactGroups { + resourceNames = append(resourceNames, contactGroup.ResourceName) + + // Context can be cancelled due to manual cancellation or the limit has been hit + if plugin.IsCancelled(ctx) { + page.NextPageToken = "" + break + } + } + if len(resourceNames) > 0 { + contactGroupNames = append(contactGroupNames, resourceNames) + } + return nil + }); err != nil { + return nil, err + } + + // execute batchGet + for _, contactGroups := range contactGroupNames { + data, err := service.ContactGroups.BatchGet().ResourceNames(contactGroups...).MaxMembers(maxMembers).Do() + if err != nil { + return nil, err + } + if len(data.Responses) > 0 { + for _, i := range data.Responses { + d.StreamListItem(ctx, i.ContactGroup) + } + } + } + + return nil, nil +} diff --git a/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_people_directory_people.go b/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_people_directory_people.go new file mode 100644 index 00000000..7b7de27a --- /dev/null +++ b/steampipe-plugin-googleworkspace/googleworkspace/table_googleworkspace_people_directory_people.go @@ -0,0 +1,87 @@ +package googleworkspace + +import ( + "context" + + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" + + "google.golang.org/api/people/v1" +) + +//// TABLE DEFINITION + +func tableGoogleWorkspacePeopleDirectoryPeople(_ context.Context) *plugin.Table { + return &plugin.Table{ + Name: "googleworkspace_people_directory_people", + Description: "Domain contacts in the authenticated user's domain directory.", + List: &plugin.ListConfig{ + Hydrate: listPeopleDirecoryPeople, + ShouldIgnoreError: isNotFoundError([]string{"404"}), + }, + Columns: peopleContacts(), + } +} + +//// LIST FUNCTION + +func listPeopleDirecoryPeople(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create service + service, err := PeopleService(ctx, d) + if err != nil { + return nil, err + } + + // Define fields the API should return + personFields := "addresses,biographies,birthdays,calendarUrls,clientData,coverPhotos,emailAddresses,events,externalIds,genders,interests,locations,memberships,metadata,miscKeywords,names,nicknames,occupations,organizations,phoneNumbers,photos,relations,sipAddresses,skills,urls,userDefined" + + // By default, API can return maximum 1000 records in a single page + maxResult := int64(1000) + + limit := d.QueryContext.Limit + if d.QueryContext.Limit != nil { + if *limit < maxResult { + maxResult = *limit + } + } + + resp := service.People.ListDirectoryPeople().ReadMask(personFields).Sources("DIRECTORY_SOURCE_TYPE_DOMAIN_PROFILE").PageSize(maxResult) + if err := resp.Pages(ctx, func(page *people.ListDirectoryPeopleResponse) error { + for _, people := range page.People { + // Since, 'names', 'birthdays', 'genders' and 'biographies' are singleton fields + var conn contacts + if people.Names != nil { + conn.Name = *people.Names[0] + } + if people.Birthdays != nil { + conn.Birthday = *people.Birthdays[0] + } + if people.Genders != nil { + conn.Gender = *people.Genders[0] + } + if people.Biographies != nil { + conn.Biography = *people.Biographies[0] + } + d.StreamListItem( + ctx, + contacts{ + conn.Name, + conn.Birthday, + conn.Gender, + conn.Biography, + *people, + }) + + // Check if the context is cancelled for query + // Break for loop if requested no of results achieved + if plugin.IsCancelled(ctx) { + page.NextPageToken = "" + break + } + } + return nil + }); err != nil { + return nil, err + } + + return nil, nil +} diff --git a/steampipe-plugin-googleworkspace/googleworkspace/utils.go b/steampipe-plugin-googleworkspace/googleworkspace/utils.go new file mode 100644 index 00000000..dd842d17 --- /dev/null +++ b/steampipe-plugin-googleworkspace/googleworkspace/utils.go @@ -0,0 +1,51 @@ +package googleworkspace + +import ( + "fmt" + "os" + + "github.com/mitchellh/go-homedir" +) + +// Returns the content of given file, or the inline JSON credential as it is +func pathOrContents(poc string) (string, error) { + if len(poc) == 0 { + return poc, nil + } + + path, err := expandPath(poc) + if err != nil { + return path, err + } + + // Check for valid file path + if _, err := os.Stat(path); err == nil { + contents, err := os.ReadFile(path) + if err != nil { + return string(contents), err + } + return string(contents), nil + } + + // Return error if content is a file path and the file doesn't exist + if len(path) > 1 && (path[0] == '/' || path[0] == '\\') { + return "", fmt.Errorf("%s: no such file or dir", path) + } + + // Return the inline content + return poc, nil +} + +// Expands the path to include the home directory if the path is prefixed with `~` +func expandPath(filePath string) (string, error) { + // Check if the path has `~` to denote the home dir + path := filePath + if path[0] == '~' { + var err error + path, err = homedir.Expand(path) + if err != nil { + return path, err + } + } + return path, nil +} diff --git a/steampipe-plugin-googleworkspace/main.go b/steampipe-plugin-googleworkspace/main.go new file mode 100644 index 00000000..bdef8ad9 --- /dev/null +++ b/steampipe-plugin-googleworkspace/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "github.com/turbot/steampipe-plugin-googleworkspace/googleworkspace" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" +) + +func main() { + plugin.Serve(&plugin.ServeOpts{ + PluginFunc: googleworkspace.Plugin}) +}