From 828ff257caf55f4bcbf28510d14071f32aa4bf4f Mon Sep 17 00:00:00 2001 From: Timo Reimann Date: Thu, 26 Sep 2024 13:38:12 +0200 Subject: [PATCH] Upgrade API dependencies Move away from deprecated PagerDuty API calls in favor of those supporting context.Context as well. --- go.mod | 14 +- go.sum | 45 +- pagerduty.go | 27 +- slack.go | 7 +- syncer.go | 4 +- .../PagerDuty/go-pagerduty/.gitignore | 5 + .../PagerDuty/go-pagerduty/CHANGELOG.md | 367 ++++++++++ .../PagerDuty/go-pagerduty/Makefile | 13 +- .../PagerDuty/go-pagerduty/README.md | 130 +++- .../PagerDuty/go-pagerduty/ability.go | 29 +- .../PagerDuty/go-pagerduty/addon.go | 99 ++- .../PagerDuty/go-pagerduty/analytics.go | 96 +++ .../PagerDuty/go-pagerduty/audit.go | 166 +++++ .../go-pagerduty/business_service.go | 200 +++++ .../PagerDuty/go-pagerduty/change_events.go | 86 +++ .../PagerDuty/go-pagerduty/client.go | 635 ++++++++++++++-- .../PagerDuty/go-pagerduty/constants.go | 5 - .../go-pagerduty/escalation_policy.go | 189 ++++- .../PagerDuty/go-pagerduty/event.go | 10 +- .../go-pagerduty/event_orchestration.go | 486 +++++++++++++ .../PagerDuty/go-pagerduty/event_v2.go | 198 ++++- .../PagerDuty/go-pagerduty/extension.go | 100 ++- .../go-pagerduty/extension_schema.go | 57 +- .../PagerDuty/go-pagerduty/incident.go | 686 ++++++++++++++---- .../PagerDuty/go-pagerduty/license.go | 84 +++ .../PagerDuty/go-pagerduty/log_entry.go | 112 ++- .../go-pagerduty/maintenance_window.go | 127 +++- .../PagerDuty/go-pagerduty/notification.go | 59 +- .../PagerDuty/go-pagerduty/on_call.go | 39 +- .../PagerDuty/go-pagerduty/priorites.go | 79 +- .../PagerDuty/go-pagerduty/response_play.go | 148 ++++ .../PagerDuty/go-pagerduty/ruleset.go | 407 +++++++++++ .../PagerDuty/go-pagerduty/schedule.go | 227 ++++-- .../PagerDuty/go-pagerduty/service.go | 324 +++++++-- .../go-pagerduty/service_dependency.go | 112 +++ .../go-pagerduty/service_integration.go | 352 +++++++++ .../PagerDuty/go-pagerduty/standard.go | 148 ++++ .../github.com/PagerDuty/go-pagerduty/tag.go | 389 ++++++++++ .../github.com/PagerDuty/go-pagerduty/team.go | 249 ++++++- .../github.com/PagerDuty/go-pagerduty/user.go | 318 ++++++-- .../PagerDuty/go-pagerduty/vendor.go | 53 +- .../PagerDuty/go-pagerduty/webhook.go | 62 +- .../google/go-cmp/cmp/cmpopts/equate.go | 57 +- .../google/go-cmp/cmp/cmpopts/ignore.go | 16 +- .../google/go-cmp/cmp/cmpopts/sort.go | 26 +- .../go-cmp/cmp/cmpopts/struct_filter.go | 2 + .../google/go-cmp/cmp/cmpopts/xform.go | 5 +- .../github.com/google/go-cmp/cmp/compare.go | 99 ++- .../cmp/{export_unsafe.go => export.go} | 4 - .../google/go-cmp/cmp/export_panic.go | 15 - .../go-cmp/cmp/internal/diff/debug_disable.go | 1 + .../go-cmp/cmp/internal/diff/debug_enable.go | 1 + .../google/go-cmp/cmp/internal/diff/diff.go | 44 +- .../cmp/internal/flags/toolchain_legacy.go | 10 - .../cmp/internal/flags/toolchain_recent.go | 10 - .../google/go-cmp/cmp/internal/value/name.go | 7 + .../value/{pointer_unsafe.go => pointer.go} | 2 - .../cmp/internal/value/pointer_purego.go | 33 - .../google/go-cmp/cmp/internal/value/zero.go | 48 -- .../github.com/google/go-cmp/cmp/options.go | 92 +-- vendor/github.com/google/go-cmp/cmp/path.go | 70 +- .../google/go-cmp/cmp/report_compare.go | 17 +- .../google/go-cmp/cmp/report_reflect.go | 26 +- .../google/go-cmp/cmp/report_slices.go | 246 ++++++- .../google/go-cmp/cmp/report_text.go | 1 + .../google/go-querystring/query/encode.go | 97 ++- .../github.com/gorilla/websocket/.gitignore | 2 +- .../github.com/gorilla/websocket/.travis.yml | 19 - vendor/github.com/gorilla/websocket/AUTHORS | 1 + vendor/github.com/gorilla/websocket/README.md | 14 +- vendor/github.com/gorilla/websocket/client.go | 245 ++++--- vendor/github.com/gorilla/websocket/conn.go | 284 +++++--- .../gorilla/websocket/conn_read_legacy.go | 21 - .../websocket/{conn_read.go => conn_write.go} | 15 +- .../gorilla/websocket/conn_write_legacy.go | 18 + vendor/github.com/gorilla/websocket/doc.go | 105 ++- vendor/github.com/gorilla/websocket/join.go | 42 ++ vendor/github.com/gorilla/websocket/json.go | 11 +- vendor/github.com/gorilla/websocket/mask.go | 1 - .../github.com/gorilla/websocket/prepared.go | 5 +- vendor/github.com/gorilla/websocket/proxy.go | 77 ++ vendor/github.com/gorilla/websocket/server.go | 138 +++- vendor/github.com/gorilla/websocket/trace.go | 19 + .../github.com/gorilla/websocket/trace_17.go | 12 + vendor/github.com/gorilla/websocket/util.go | 165 +++-- .../gorilla/websocket/x_net_proxy.go | 473 ++++++++++++ vendor/github.com/pkg/errors/.gitignore | 24 - vendor/github.com/pkg/errors/.travis.yml | 11 - vendor/github.com/pkg/errors/LICENSE | 23 - vendor/github.com/pkg/errors/README.md | 52 -- vendor/github.com/pkg/errors/appveyor.yml | 32 - vendor/github.com/pkg/errors/errors.go | 269 ------- vendor/github.com/pkg/errors/stack.go | 178 ----- vendor/github.com/slack-go/slack/.gitignore | 1 + .../github.com/slack-go/slack/.golangci.yml | 14 + .../slack-go/slack/.gometalinter.json | 14 - vendor/github.com/slack-go/slack/.travis.yml | 39 - vendor/github.com/slack-go/slack/CHANGELOG.md | 44 ++ vendor/github.com/slack-go/slack/README.md | 33 +- vendor/github.com/slack-go/slack/apps.go | 72 ++ .../github.com/slack-go/slack/attachments.go | 9 +- vendor/github.com/slack-go/slack/audit.go | 152 ++++ vendor/github.com/slack-go/slack/auth.go | 41 +- vendor/github.com/slack-go/slack/block.go | 60 +- .../github.com/slack-go/slack/block_action.go | 4 +- .../slack-go/slack/block_context.go | 2 +- .../github.com/slack-go/slack/block_conv.go | 80 +- .../slack-go/slack/block_element.go | 341 ++++++++- .../github.com/slack-go/slack/block_header.go | 38 + .../github.com/slack-go/slack/block_image.go | 20 +- .../github.com/slack-go/slack/block_input.go | 16 +- .../github.com/slack-go/slack/block_object.go | 50 +- .../slack-go/slack/block_rich_text.go | 528 ++++++++++++++ .../github.com/slack-go/slack/block_video.go | 65 ++ vendor/github.com/slack-go/slack/bookmarks.go | 169 +++++ vendor/github.com/slack-go/slack/bots.go | 25 +- vendor/github.com/slack-go/slack/channels.go | 454 +----------- vendor/github.com/slack-go/slack/chat.go | 328 ++++++--- .../github.com/slack-go/slack/conversation.go | 306 ++++++-- vendor/github.com/slack-go/slack/dialog.go | 4 +- .../slack-go/slack/dialog_select.go | 14 + .../github.com/slack-go/slack/dialog_text.go | 2 +- vendor/github.com/slack-go/slack/dnd.go | 27 +- vendor/github.com/slack-go/slack/emoji.go | 6 +- vendor/github.com/slack-go/slack/errors.go | 1 + vendor/github.com/slack-go/slack/files.go | 333 +++++++-- vendor/github.com/slack-go/slack/groups.go | 348 --------- vendor/github.com/slack-go/slack/history.go | 1 + vendor/github.com/slack-go/slack/im.go | 133 ---- vendor/github.com/slack-go/slack/info.go | 12 +- .../github.com/slack-go/slack/interactions.go | 135 +++- .../slack/{ => internal/backoff}/backoff.go | 15 +- .../slack/internal/errorsx/errorsx.go | 9 + vendor/github.com/slack-go/slack/logger.go | 2 +- vendor/github.com/slack-go/slack/logo.png | Bin 0 -> 52440 bytes vendor/github.com/slack-go/slack/manifests.go | 297 ++++++++ vendor/github.com/slack-go/slack/messageID.go | 20 +- vendor/github.com/slack-go/slack/messages.go | 74 +- vendor/github.com/slack-go/slack/metadata.go | 7 + vendor/github.com/slack-go/slack/misc.go | 119 +-- vendor/github.com/slack-go/slack/oauth.go | 112 ++- vendor/github.com/slack-go/slack/pins.go | 14 +- vendor/github.com/slack-go/slack/reactions.go | 22 +- vendor/github.com/slack-go/slack/reminders.go | 85 ++- .../github.com/slack-go/slack/remotefiles.go | 309 ++++++++ vendor/github.com/slack-go/slack/search.go | 4 + vendor/github.com/slack-go/slack/security.go | 12 +- vendor/github.com/slack-go/slack/slack.go | 37 +- .../slack-go/slack/slackutilsx/slackutilsx.go | 6 +- vendor/github.com/slack-go/slack/slash.go | 64 +- .../github.com/slack-go/slack/socket_mode.go | 34 + vendor/github.com/slack-go/slack/stars.go | 38 +- .../slack-go/slack/status_code_error.go | 28 + vendor/github.com/slack-go/slack/team.go | 129 +++- vendor/github.com/slack-go/slack/tokens.go | 52 ++ .../github.com/slack-go/slack/usergroups.go | 125 +++- vendor/github.com/slack-go/slack/users.go | 290 ++++++-- vendor/github.com/slack-go/slack/views.go | 81 ++- vendor/github.com/slack-go/slack/webhooks.go | 35 + .../slack-go/slack/webhooks_go112.go | 34 - .../slack-go/slack/webhooks_go113.go | 33 - .../slack-go/slack/websocket_internals.go | 1 + .../slack-go/slack/websocket_managed_conn.go | 57 +- .../slack-go/slack/websocket_reactions.go | 12 +- .../slack-go/slack/websocket_subteam.go | 4 +- .../slack-go/slack/workflow_step.go | 101 +++ .../slack-go/slack/workflow_step_execute.go | 85 +++ vendor/golang.org/x/xerrors/LICENSE | 27 - vendor/golang.org/x/xerrors/PATENTS | 22 - vendor/golang.org/x/xerrors/README | 2 - vendor/golang.org/x/xerrors/adaptor.go | 193 ----- vendor/golang.org/x/xerrors/codereview.cfg | 1 - vendor/golang.org/x/xerrors/doc.go | 22 - vendor/golang.org/x/xerrors/errors.go | 33 - vendor/golang.org/x/xerrors/fmt.go | 187 ----- vendor/golang.org/x/xerrors/format.go | 34 - vendor/golang.org/x/xerrors/frame.go | 56 -- .../golang.org/x/xerrors/internal/internal.go | 8 - vendor/golang.org/x/xerrors/wrap.go | 106 --- vendor/gopkg.in/yaml.v2/.travis.yml | 1 + vendor/gopkg.in/yaml.v2/apic.go | 5 + vendor/gopkg.in/yaml.v2/yaml.go | 14 +- vendor/modules.txt | 32 +- 183 files changed, 12676 insertions(+), 4463 deletions(-) create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/analytics.go create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/audit.go create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/business_service.go create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/change_events.go delete mode 100644 vendor/github.com/PagerDuty/go-pagerduty/constants.go create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/event_orchestration.go create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/license.go create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/response_play.go create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/ruleset.go create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/service_dependency.go create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/service_integration.go create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/standard.go create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/tag.go rename vendor/github.com/google/go-cmp/cmp/{export_unsafe.go => export.go} (95%) delete mode 100644 vendor/github.com/google/go-cmp/cmp/export_panic.go delete mode 100644 vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_legacy.go delete mode 100644 vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_recent.go rename vendor/github.com/google/go-cmp/cmp/internal/value/{pointer_unsafe.go => pointer.go} (97%) delete mode 100644 vendor/github.com/google/go-cmp/cmp/internal/value/pointer_purego.go delete mode 100644 vendor/github.com/google/go-cmp/cmp/internal/value/zero.go delete mode 100644 vendor/github.com/gorilla/websocket/.travis.yml delete mode 100644 vendor/github.com/gorilla/websocket/conn_read_legacy.go rename vendor/github.com/gorilla/websocket/{conn_read.go => conn_write.go} (52%) create mode 100644 vendor/github.com/gorilla/websocket/conn_write_legacy.go create mode 100644 vendor/github.com/gorilla/websocket/join.go create mode 100644 vendor/github.com/gorilla/websocket/proxy.go create mode 100644 vendor/github.com/gorilla/websocket/trace.go create mode 100644 vendor/github.com/gorilla/websocket/trace_17.go create mode 100644 vendor/github.com/gorilla/websocket/x_net_proxy.go delete mode 100644 vendor/github.com/pkg/errors/.gitignore delete mode 100644 vendor/github.com/pkg/errors/.travis.yml delete mode 100644 vendor/github.com/pkg/errors/LICENSE delete mode 100644 vendor/github.com/pkg/errors/README.md delete mode 100644 vendor/github.com/pkg/errors/appveyor.yml delete mode 100644 vendor/github.com/pkg/errors/errors.go delete mode 100644 vendor/github.com/pkg/errors/stack.go create mode 100644 vendor/github.com/slack-go/slack/.golangci.yml delete mode 100644 vendor/github.com/slack-go/slack/.gometalinter.json delete mode 100644 vendor/github.com/slack-go/slack/.travis.yml create mode 100644 vendor/github.com/slack-go/slack/apps.go create mode 100644 vendor/github.com/slack-go/slack/audit.go create mode 100644 vendor/github.com/slack-go/slack/block_header.go create mode 100644 vendor/github.com/slack-go/slack/block_rich_text.go create mode 100644 vendor/github.com/slack-go/slack/block_video.go create mode 100644 vendor/github.com/slack-go/slack/bookmarks.go rename vendor/github.com/slack-go/slack/{ => internal/backoff}/backoff.go (79%) create mode 100644 vendor/github.com/slack-go/slack/logo.png create mode 100644 vendor/github.com/slack-go/slack/manifests.go create mode 100644 vendor/github.com/slack-go/slack/metadata.go create mode 100644 vendor/github.com/slack-go/slack/remotefiles.go create mode 100644 vendor/github.com/slack-go/slack/socket_mode.go create mode 100644 vendor/github.com/slack-go/slack/status_code_error.go create mode 100644 vendor/github.com/slack-go/slack/tokens.go delete mode 100644 vendor/github.com/slack-go/slack/webhooks_go112.go delete mode 100644 vendor/github.com/slack-go/slack/webhooks_go113.go create mode 100644 vendor/github.com/slack-go/slack/workflow_step.go create mode 100644 vendor/github.com/slack-go/slack/workflow_step_execute.go delete mode 100644 vendor/golang.org/x/xerrors/LICENSE delete mode 100644 vendor/golang.org/x/xerrors/PATENTS delete mode 100644 vendor/golang.org/x/xerrors/README delete mode 100644 vendor/golang.org/x/xerrors/adaptor.go delete mode 100644 vendor/golang.org/x/xerrors/codereview.cfg delete mode 100644 vendor/golang.org/x/xerrors/doc.go delete mode 100644 vendor/golang.org/x/xerrors/errors.go delete mode 100644 vendor/golang.org/x/xerrors/fmt.go delete mode 100644 vendor/golang.org/x/xerrors/format.go delete mode 100644 vendor/golang.org/x/xerrors/frame.go delete mode 100644 vendor/golang.org/x/xerrors/internal/internal.go delete mode 100644 vendor/golang.org/x/xerrors/wrap.go diff --git a/go.mod b/go.mod index c625205..5e471ff 100644 --- a/go.mod +++ b/go.mod @@ -3,21 +3,19 @@ module github.com/timoreimann/pdsync go 1.23 require ( - github.com/PagerDuty/go-pagerduty v1.1.2 - github.com/google/go-cmp v0.5.4 + github.com/PagerDuty/go-pagerduty v1.8.0 + github.com/google/go-cmp v0.6.0 github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2 - github.com/slack-go/slack v0.6.3 + github.com/slack-go/slack v0.14.0 github.com/urfave/cli/v2 v2.1.1 - gopkg.in/yaml.v2 v2.2.8 + gopkg.in/yaml.v2 v2.4.0 ) require ( github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect - github.com/google/go-querystring v1.0.0 // indirect - github.com/gorilla/websocket v1.2.0 // indirect - github.com/pkg/errors v0.8.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/gorilla/websocket v1.4.2 // indirect github.com/russross/blackfriday/v2 v2.0.1 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect - golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect ) diff --git a/go.sum b/go.sum index c74c7f3..84fba69 100644 --- a/go.sum +++ b/go.sum @@ -1,56 +1,39 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/PagerDuty/go-pagerduty v1.1.2 h1:pTY5GKmmR88EeeI+9/LR+dKL2Chohz3L5yroqoUl+lQ= -github.com/PagerDuty/go-pagerduty v1.1.2/go.mod h1:ZKUzEnyuEMTCMwuzP5NyQIwPx+ThSKBNUva2/ns0Op8= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/PagerDuty/go-pagerduty v1.8.0 h1:MTFqTffIcAervB83U7Bx6HERzLbyaSPL/+oxH3zyluI= +github.com/PagerDuty/go-pagerduty v1.8.0/go.mod h1:nzIeAqyFSJAFkjWKvMzug0JtwDg+V+UoCWjFrfFH5mI= github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 h1:SKI1/fuSdodxmNNyVBR8d7X/HuLnRpvvFO0AgyQk764= github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 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/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTMQQ= -github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/google/go-cmp v0.5.2/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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2 h1:JAEbJn3j/FrhdWA9jW8B5ajsLIjeuEHLi8xE4fk997o= github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/nlopes/slack v0.6.0/go.mod h1:JzQ9m3PMAqcpeCam7UaHSuBuupz7CmpjehYMayT6YOk= -github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= -github.com/pkg/errors v0.8.0/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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/slack-go/slack v0.6.3 h1:qU037g8gQ71EuH6S9zYKnvYrEUj0fLFH4HFekFqBoRU= -github.com/slack-go/slack v0.6.3/go.mod h1:HE4RwNe7YpOg/F0vqo5PwXH3Hki31TplTvKRW9dGGaw= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/slack-go/slack v0.14.0 h1:6c0UTfbRnvRssZUsZ2qe0Iu07VAMPjRqOa6oX8ewF4k= +github.com/slack-go/slack v0.14.0/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k= github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/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= diff --git a/pagerduty.go b/pagerduty.go index 02f7714..b9591e9 100644 --- a/pagerduty.go +++ b/pagerduty.go @@ -1,6 +1,7 @@ package main import ( + "context" "errors" "fmt" "net/http" @@ -49,16 +50,16 @@ func newPagerDutyClient(token string) *pagerDutyClient { } } -func (cl *pagerDutyClient) getSchedule(id, name string) (*pdSchedule, error) { +func (cl *pagerDutyClient) getSchedule(ctx context.Context, id, name string) (*pdSchedule, error) { if id != "" { - schedule, err := cl.getScheduleByID(id) + schedule, err := cl.getScheduleByID(ctx, id) if err != nil { return nil, fmt.Errorf("failed to get schedule by ID: %s", err) } return schedule, nil } - schedule, err := cl.getScheduleByName(name) + schedule, err := cl.getScheduleByName(ctx, name) if err != nil { return nil, fmt.Errorf("failed to get schedules by name: %s", err) } @@ -66,7 +67,7 @@ func (cl *pagerDutyClient) getSchedule(id, name string) (*pdSchedule, error) { return schedule, nil } -func (cl *pagerDutyClient) getScheduleByID(scheduleID string) (*pdSchedule, error) { +func (cl *pagerDutyClient) getScheduleByID(ctx context.Context, scheduleID string) (*pdSchedule, error) { if scheduleID == "" { return nil, errors.New("schedule ID is missing") } @@ -75,7 +76,7 @@ func (cl *pagerDutyClient) getScheduleByID(scheduleID string) (*pdSchedule, erro var schedule *pagerduty.Schedule rErr := retryOnPagerDutyRateLimit(func() error { var err error - schedule, err = cl.GetSchedule(scheduleID, pagerduty.GetScheduleOptions{}) + schedule, err = cl.GetScheduleWithContext(ctx, scheduleID, pagerduty.GetScheduleOptions{}) return err }) if rErr != nil { @@ -92,14 +93,14 @@ func (cl *pagerDutyClient) getScheduleByID(scheduleID string) (*pdSchedule, erro }, nil } -func (cl *pagerDutyClient) getScheduleByName(scheduleName string) (*pdSchedule, error) { +func (cl *pagerDutyClient) getScheduleByName(ctx context.Context, scheduleName string) (*pdSchedule, error) { if scheduleName == "" { return nil, errors.New("schedule name is missing") } var err error cl.pdSchedulesByNameOnce.Do(func() { - cl.pdSchedulesByName, err = cl.getAllSchedulesByName() + cl.pdSchedulesByName, err = cl.getAllSchedulesByName(ctx) }) if err != nil { return nil, fmt.Errorf("failed to get all schedules by name: %s", err) @@ -113,9 +114,9 @@ func (cl *pagerDutyClient) getScheduleByName(scheduleName string) (*pdSchedule, return &pdSchedule, nil } -func (cl *pagerDutyClient) getAllSchedulesByName() (map[string]pdSchedule, error) { +func (cl *pagerDutyClient) getAllSchedulesByName(ctx context.Context) (map[string]pdSchedule, error) { pdSchedules := map[string]pdSchedule{} - alo := pagerduty.APIListObject{ + opts := pagerduty.ListSchedulesOptions{ Limit: 100, } fmt.Println("Collecting schedules") @@ -124,7 +125,7 @@ func (cl *pagerDutyClient) getAllSchedulesByName() (map[string]pdSchedule, error var schedulesResp *pagerduty.ListSchedulesResponse rErr := retryOnPagerDutyRateLimit(func() error { var err error - schedulesResp, err = cl.ListSchedules(pagerduty.ListSchedulesOptions{APIListObject: alo}) + schedulesResp, err = cl.ListSchedulesWithContext(ctx, opts) return err }) if rErr != nil { @@ -141,16 +142,16 @@ func (cl *pagerDutyClient) getAllSchedulesByName() (map[string]pdSchedule, error if !schedulesResp.APIListObject.More { break } - alo.Offset = alo.Offset + alo.Limit + opts.Offset = opts.Offset + opts.Limit } return pdSchedules, nil } -func (cl *pagerDutyClient) getOnCallUser(schedule pdSchedule) (pagerduty.User, error) { +func (cl *pagerDutyClient) getOnCallUser(ctx context.Context, schedule pdSchedule) (pagerduty.User, error) { now := time.Now() fmt.Printf("Getting on-call users for schedule %s\n", schedule) - onCallUsers, err := cl.ListOnCallUsers(schedule.id, pagerduty.ListOnCallUsersOptions{ + onCallUsers, err := cl.ListOnCallUsersWithContext(ctx, schedule.id, pagerduty.ListOnCallUsersOptions{ Since: now.Add(-1 * time.Second).Format(time.RFC3339), Until: now.Format(time.RFC3339), }) diff --git a/slack.go b/slack.go index 39873b3..f523659 100644 --- a/slack.go +++ b/slack.go @@ -112,7 +112,7 @@ func (metaClient *slackMetaClient) getChannels(ctx context.Context) (channelList var err error channels, nextCursor, err = metaClient.slackClient.GetConversationsContext(ctx, &slack.GetConversationsParameters{ Cursor: cursor, - ExcludeArchived: "true", + ExcludeArchived: true, Limit: 200, Types: metaClient.channelTypes, }) @@ -136,7 +136,10 @@ func (metaClient *slackMetaClient) getChannels(ctx context.Context) (channelList } func (metaClient *slackMetaClient) getChannelByID(ctx context.Context, id string) (*slack.Channel, error) { - return metaClient.slackClient.GetConversationInfoContext(ctx, id, false) + return metaClient.slackClient.GetConversationInfoContext(ctx, &slack.GetConversationInfoInput{ + ChannelID: id, + IncludeLocale: true, + }) } func (metaClient *slackMetaClient) getUserGroups(ctx context.Context) ([]UserGroup, error) { diff --git a/syncer.go b/syncer.go index 20352e1..cfb5c73 100644 --- a/syncer.go +++ b/syncer.go @@ -64,7 +64,7 @@ func (sp syncerParams) createSlackSyncs(ctx context.Context, cfg config) ([]runS pdSchedules := pdSchedules{} fmt.Printf("Slack sync %s: Getting PagerDuty schedules\n", slSync.name) for _, schedule := range cfgSlSync.Schedules { - pdSchedule, err := sp.pdClient.getSchedule(schedule.ID, schedule.Name) + pdSchedule, err := sp.pdClient.getSchedule(ctx, schedule.ID, schedule.Name) if err != nil { return nil, fmt.Errorf("failed to create slack sync %q: failed to get schedule %s: %s", slSync.name, schedule, err) } @@ -146,7 +146,7 @@ func (s *syncer) runSlackSync(ctx context.Context, slackSync runSlackSync) error slackUserIDByScheduleName := map[string]string{} for _, schedule := range slackSync.pdSchedules { fmt.Printf("Processing schedule %s\n", schedule) - onCallUser, err := s.pdClient.getOnCallUser(schedule) + onCallUser, err := s.pdClient.getOnCallUser(ctx, schedule) if err != nil { return fmt.Errorf("failed to get on call user for schedule %q: %s", schedule.name, err) } diff --git a/vendor/github.com/PagerDuty/go-pagerduty/.gitignore b/vendor/github.com/PagerDuty/go-pagerduty/.gitignore index b462291..5162bc9 100644 --- a/vendor/github.com/PagerDuty/go-pagerduty/.gitignore +++ b/vendor/github.com/PagerDuty/go-pagerduty/.gitignore @@ -2,3 +2,8 @@ bin/* *.swp pd vendor/ +.vscode/ +.idea/ + +# development API key(s) +*.key diff --git a/vendor/github.com/PagerDuty/go-pagerduty/CHANGELOG.md b/vendor/github.com/PagerDuty/go-pagerduty/CHANGELOG.md index 66da574..9e1cc9f 100644 --- a/vendor/github.com/PagerDuty/go-pagerduty/CHANGELOG.md +++ b/vendor/github.com/PagerDuty/go-pagerduty/CHANGELOG.md @@ -1,5 +1,372 @@ # Changelog +## [v1.8.0](https://github.com/PagerDuty/go-pagerduty/tree/v1.8.0) (2024-01-09) +### What's Changed +* Adds resolved_at and updated_at fields to Incident by @surik in https://github.com/PagerDuty/go-pagerduty/pull/482 +* Adds support for auto_pause_notifications_parameters by @darrendao in https://github.com/PagerDuty/go-pagerduty/pull/490 +* Fixes ResponderRequest unmarshalling of IncidentResponders by @allyjweir in https://github.com/PagerDuty/go-pagerduty/pull/493 +* Adds support for license and licenses allocated based operations #480 by @gerardocorea in https://github.com/PagerDuty/go-pagerduty/pull/494 +* Refactors nil *string initialization by @typeid in https://github.com/PagerDuty/go-pagerduty/pull/488 +* [CSGI-1827] Add Standards support by @imjaroiswebdev in https://github.com/PagerDuty/go-pagerduty/pull/499 +* Updated README.md replacing deprecated calls with corresponding new ones by @oleksiypavlenko in https://github.com/PagerDuty/go-pagerduty/pull/498 +* [CSGI-1984] Add `From` field to `ListResponsePlays` method options by @imjaroiswebdev in https://github.com/PagerDuty/go-pagerduty/pull/502 +* Handle invalid ChangeEvent APIErrorObject response by @icholy in https://github.com/PagerDuty/go-pagerduty/pull/479 +* [CSGI-2365] Add client User Agent overwrite capability by @imjaroiswebdev in https://github.com/PagerDuty/go-pagerduty/pull/503 +* Updates go to 1.19 and a few dependencies by @ChuckCrawford in https://github.com/PagerDuty/go-pagerduty/pull/504 +* Bump golang.org/x/crypto from 0.16.0 to 0.17.0 by @dependabot in https://github.com/PagerDuty/go-pagerduty/pull/505 + +### New Contributors +* @surik made their first contribution in https://github.com/PagerDuty/go-pagerduty/pull/482 +* @darrendao made their first contribution in https://github.com/PagerDuty/go-pagerduty/pull/490 +* @allyjweir made their first contribution in https://github.com/PagerDuty/go-pagerduty/pull/493 +* @gerardocorea made their first contribution in https://github.com/PagerDuty/go-pagerduty/pull/494 +* @typeid made their first contribution in https://github.com/PagerDuty/go-pagerduty/pull/488 +* @imjaroiswebdev made their first contribution in https://github.com/PagerDuty/go-pagerduty/pull/499 +* @oleksiypavlenko made their first contribution in https://github.com/PagerDuty/go-pagerduty/pull/498 +* @icholy made their first contribution in https://github.com/PagerDuty/go-pagerduty/pull/479 +* @dependabot made their first contribution in https://github.com/PagerDuty/go-pagerduty/pull/505 + +**Full Changelog**: https://github.com/PagerDuty/go-pagerduty/compare/v1.7.0...v1.8.0 + +## [v1.7.0](https://github.com/PagerDuty/go-pagerduty/tree/v1.7.0) (2023-05-17) + +### What's Changed +* Upgades Go and dependencies by @ChuckCrawford in https://github.com/PagerDuty/go-pagerduty/pull/466 +* Add Incident Notification Subscribers by @caveman280 in https://github.com/PagerDuty/go-pagerduty/pull/461 +* Implement Event Orchestrations API by @EronWright in https://github.com/PagerDuty/go-pagerduty/pull/450 + +### New Contributors +* @caveman280 made their first contribution in https://github.com/PagerDuty/go-pagerduty/pull/461 +* @EronWright made their first contribution in https://github.com/PagerDuty/go-pagerduty/pull/450 + +**Full Changelog**: https://github.com/PagerDuty/go-pagerduty/compare/v1.6.0...v1.7.0 + +## [v1.6.0](https://github.com/PagerDuty/go-pagerduty/tree/v1.6.0) (2022-09-21) + +[Full Changelog](https://github.com/PagerDuty/go-pagerduty/compare/v1.5.0...1.6.0) +[Milestone](https://github.com/PagerDuty/go-pagerduty/milestone/7) + +### A Quick Word from Us +Hello there! We just wanted to drop in and address the elephant in the room. In a [previous release](https://github.com/PagerDuty/go-pagerduty/releases/tag/v1.5.0) we made a promise to maintain API compatibility for future v1 releases. This release contains a few changes that may require minor updates to the way your code interacts with API request and response objects. These changes are necessary in order to make these APIs work properly for as many of you as possible. + +One of our goals with this client library is to provide a working client that properly interacts with the [PagerDuty Public API](https://developer.pagerduty.com/api-reference/). While we remain committed to not breaking API compatibility for reasons such as "making the library [better | easier to use | etc.]"; we believe it is in all of our best interest to make this client library work. + +As always, we welcome your feedback on this decision via a GitHub issue. + +### Highlights +* Returns custom error type `EventsAPIV2Error` for errors that occur with Events API (V2) calls by @theckman in https://github.com/PagerDuty/go-pagerduty/pull/419 +* List notifications API now works when using `includes` by @jaimegago in https://github.com/PagerDuty/go-pagerduty/pull/439 +* Improves support for macOS builds by @mjlshen in https://github.com/PagerDuty/go-pagerduty/pull/434 +* Fix potential panic when debug request captures are enabled by @theckman in https://github.com/PagerDuty/go-pagerduty/pull/443 +* Fixes memory leak by @attilakunelwood in https://github.com/PagerDuty/go-pagerduty/pull/453 +* Prevent leak on error responses by @ChuckCrawford in https://github.com/PagerDuty/go-pagerduty/pull/454 + +### Breaking Changes +* Fix unmarshaling error with AlertGroupingParameters timeout by @mjlshen in https://github.com/PagerDuty/go-pagerduty/pull/459 +* Fix parsing bug for AlertGroupingParameters by @mjlshen in https://github.com/PagerDuty/go-pagerduty/pull/448 +* Fix ResponderRequest regression by @ehlerorngard in https://github.com/PagerDuty/go-pagerduty/pull/452 + +### New Contributors +* @jaimegago made their first contribution in https://github.com/PagerDuty/go-pagerduty/pull/439 +* @mjlshen made their first contribution in https://github.com/PagerDuty/go-pagerduty/pull/434 +* @attilakunelwood made their first contribution in https://github.com/PagerDuty/go-pagerduty/pull/453 +* @ChuckCrawford made their first contribution in https://github.com/PagerDuty/go-pagerduty/pull/454 +* @ehlerorngard made their first contribution in https://github.com/PagerDuty/go-pagerduty/pull/452 + +## [v1.5.1](https://github.com/PagerDuty/go-pagerduty/tree/v1.5.1) (2022-04-24) - Bug Fixes + +[Milestone](https://github.com/PagerDuty/go-pagerduty/milestone/8) +[Full Changelog](https://github.com/PagerDuty/go-pagerduty/compare/v1.5.0...v1.5.1) + +**Highlights** + +- Fix panic that would occur when debug request capturing is enabled, and request has a nil HTTP body (GET request) +- Fix options for ListNotifications, where URL parameters weren't being set correctly. + +**Closed issues** + +None + +**Merged pull requests** + +- Backport fix for panic when debug request captures are enabled [\#444](https://github.com/PagerDuty/go-pagerduty/pull/444) ([theckman](https://github.com/theckman)) +- Backport: Fix list notifications options [\#445](https://github.com/PagerDuty/go-pagerduty/pull/445) ([jaimegago](https://github.com/jaimegago), backported by [theckman](https://github.com/theckman)) + +## [v1.5.0](https://github.com/PagerDuty/go-pagerduty/tree/v1.5.0) (2022-01-22) - BREAKING CHANGES + +[Milestone](https://github.com/PagerDuty/go-pagerduty/milestone/2) +[Full Changelog](https://github.com/PagerDuty/go-pagerduty/compare/v1.4.3...v1.5.0) + +**NOTICE** + +This release is special, and intentionally includes breaking API changes without +bumping the module's major version. We apologize for any inconveience this +causes, but we felt this approach was better than incurring the cost of +releasing v2 today. Specifically, we believed it was best to break the API, so +that you became aware of features you depended on that were not working as you +expected. We welcome your feedback on this decision via a GitHub issue. + +These changes largely fix API issues that would have made it impossible for the +this module to be used correctly. Most often this is due to incorrect or invalid +struct fields, others were a lack of required inputs to specific actions. + +We commit henceforth to maintaining API compatibility for future v1 releases. + +**Highlights** + +- Add support for handling signature verification of V3 Webhook requests. +- Update `APIError` type to provide more helpful error strings. +- Add support for API debugging, by allowing capture of the request/response from the API. +- Added various missing fields to different structs. +- Add support for response plays, fetching audit records, and setting up email filters. + +**Breaking changes** + +- Fix the ResponderRequest input/output structures [\#328](https://github.com/PagerDuty/go-pagerduty/pull/328) ([CerealBoy](https://github.com/CerealBoy)) +- Fix overlapping struct fields in Incident, Service, and User types [\#332](https://github.com/PagerDuty/go-pagerduty/pull/332) ([theckman](https://github.com/theckman)) +- Remove *http.Response return from different API methods. [\#357](https://github.com/PagerDuty/go-pagerduty/pull/357) [\#358](https://github.com/PagerDuty/go-pagerduty/pull/358) [\#359](https://github.com/PagerDuty/go-pagerduty/pull/359) [\#360](https://github.com/PagerDuty/go-pagerduty/pull/360) [\#361](https://github.com/PagerDuty/go-pagerduty/pull/361) ([theckman](https://github.com/theckman)) +- Add missing required From parameter to ManageIncidentAlerts [\#380](https://github.com/PagerDuty/go-pagerduty/pull/380) ([theckman](https://github.com/theckman)) +- Fix mismatches between REST API and struct definitions [\#396](https://github.com/PagerDuty/go-pagerduty/pull/396) [\#414](https://github.com/PagerDuty/go-pagerduty/pull/414) ([theckman](https://github.com/theckman)) +- Update pagination query params to conform to API spec [\#405](https://github.com/PagerDuty/go-pagerduty/pull/405) ([theckman](https://github.com/theckman) + +**Closed issues** + +- incident.ID or incident.Id [\#218](https://github.com/PagerDuty/go-pagerduty/issues/218) ([mblaschke](https://github.com/mblaschke)) +- Improper unmarshalling [\#232](https://github.com/PagerDuty/go-pagerduty/issues/232) ([Erog38](https://github.com/Erog38)) +- Update initialisms / acronyms to be capitalized [\#268](https://github.com/PagerDuty/go-pagerduty/issues/268) ([theckman](https://github.com/theckman)) +- Add IncidentDetails.Title field and mark IncidentDetails.Description as deprecated [\#277](https://github.com/PagerDuty/go-pagerduty/issues/277) ([theckman](https://github.com/theckman)) +- Remove *http.Response returns from API methods [\#305](https://github.com/PagerDuty/go-pagerduty/issues/305) ([theckman](https://github.com/theckman)) +- Add ability to create or update integrations with email filter rules [\#315](https://github.com/PagerDuty/go-pagerduty/issues/315) ([gerardocorea](https://github.com/gerardocorea)) +- EscalationRule struct should accept a slice of APIReference rather then APIObject for Targets [\#316](https://github.com/PagerDuty/go-pagerduty/issues/316) ([gerardocorea](https://github.com/gerardocorea)) +- User slice fields missing omitempty [\#343](https://github.com/PagerDuty/go-pagerduty/issues/343) ([theckman](https://github.com/theckman)) +- Should all fields in Service type be omitempty [\#348](https://github.com/PagerDuty/go-pagerduty/issues/348) ([callumj](https://github.com/callumj)) +- ManageIncidentsOptions doesn't take EscalationLevel [\#364](https://github.com/PagerDuty/go-pagerduty/issues/364) ([sim1s](https://github.com/sim1s)) +- Setting conference information in new incident [\#373](https://github.com/PagerDuty/go-pagerduty/issues/373) ([bparlidoordash](https://github.com/bparlidoordash)) +- Validate that all structure formats and method signatures work with PagerDuty API [\#389](https://github.com/PagerDuty/go-pagerduty/issues/389) ([theckman](https://github.com/theckman)) +- When creating an incident, consumers shouldn't need to set the Type field [\#390](https://github.com/PagerDuty/go-pagerduty/issues/390) ([theckman](https://github.com/theckman)) +- Reduce code duplication in analytics.go [\#393](https://github.com/PagerDuty/go-pagerduty/issues/393) ([theckman](https://github.com/theckman)) +- Add support for fetching Audit Records [\#394](https://github.com/PagerDuty/go-pagerduty/issues/394) ([theckman](https://github.com/theckman)) + +**Merged pull requests** + +- Add assignees to log entry [\#237](https://github.com/PagerDuty/go-pagerduty/pull/237) ([tautvydass](https://github.com/tautvydass)) +- Add support for better API debugging; start v1.5.0 development [\#325](https://github.com/PagerDuty/go-pagerduty/pull/325) ([theckman](https://github.com/theckman)) +- Fix the ResponderRequest input/output structures [\#328](https://github.com/PagerDuty/go-pagerduty/pull/328) ([CerealBoy](https://github.com/CerealBoy)) +- Fix overlapping struct fields & last golint errors [\#332](https://github.com/PagerDuty/go-pagerduty/pull/332) ([theckman](https://github.com/theckman)) +- Add comment indicating IncidentDetails.Description is deprecated [\#333](https://github.com/PagerDuty/go-pagerduty/pull/333) ([theckman](https://github.com/theckman)) +- Update APIError.Error() to provide more helpful error messages [\#334](https://github.com/PagerDuty/go-pagerduty/pull/334) ([theckman](https://github.com/theckman)) +- Add comment above IncidentDetails.Alerts field explaining behaviors [\#335](https://github.com/PagerDuty/go-pagerduty/pull/335) ([theckman](https://github.com/theckman)) +- Correct formatting of deprecation notices. [\#340](https://github.com/PagerDuty/go-pagerduty/pull/340) ([dsymonds](https://github.com/dsymonds)) +- Fix `pd schedule override create`. [\#341](https://github.com/PagerDuty/go-pagerduty/pull/341) ([dsymonds](https://github.com/dsymonds)) +- allow setting suppress to false [\#345](https://github.com/PagerDuty/go-pagerduty/pull/345) ([cluarkhpe](https://github.com/cluarkhpe)) +- Swap two transposed words in the README file [\#350](https://github.com/PagerDuty/go-pagerduty/pull/350) ([theckman](https://github.com/theckman)) +- Fixing link to PD API Reference [\#356](https://github.com/PagerDuty/go-pagerduty/pull/356) ([stmcallister](https://github.com/stmcallister)) +- Remove returned *http.Response from incident-related methods [\#357](https://github.com/PagerDuty/go-pagerduty/pull/357) ([theckman](https://github.com/theckman)) +- Remove returned *http.Response from business svc related methods [\#358](https://github.com/PagerDuty/go-pagerduty/pull/358) ([theckman](https://github.com/theckman)) +- Remove returned *http.Response from svc dependency related methods [\#359](https://github.com/PagerDuty/go-pagerduty/pull/359) ([theckman](https://github.com/theckman)) +- Remove returned *http.Response from tag-related methods [\#360](https://github.com/PagerDuty/go-pagerduty/pull/360) ([theckman](https://github.com/theckman)) +- Remove returned *http.Response from ruleset-related methods [\#361](https://github.com/PagerDuty/go-pagerduty/pull/361) ([theckman](https://github.com/theckman)) +- implement missing maintenance-window subcommands [\#363](https://github.com/PagerDuty/go-pagerduty/pull/363) ([Hsn723](https://github.com/Hsn723)) +- Add json field incidents_responders to Incident struct [\#365](https://github.com/PagerDuty/go-pagerduty/pull/365) ([sostakas](https://github.com/sostakas)) +- Adding escalation level to ManageIncidentOptions [\#366](https://github.com/PagerDuty/go-pagerduty/pull/366) ([sim1s](https://github.com/sim1s)) +- Add v3 webhook signature verification [\#370](https://github.com/PagerDuty/go-pagerduty/pull/370) ([theckman](https://github.com/theckman)) +- Fix test after merging #332 (2f47dfc62321b) [\#371](https://github.com/PagerDuty/go-pagerduty/pull/371) ([theckman](https://github.com/theckman)) +- Add title to ManageIncidentOptions [\#372](https://github.com/PagerDuty/go-pagerduty/pull/372) ([d33d33](https://github.com/d33d33)) +- Add Service and User to LogEntry [\#377](https://github.com/PagerDuty/go-pagerduty/pull/377) ([theckman](https://github.com/theckman)) +- Add missing required parameeter to ManageIncidentAlerts [\#380](https://github.com/PagerDuty/go-pagerduty/pull/380) ([theckman](https://github.com/theckman)) +- Handle unexpected type changes in PagerDuty REST API error responses [\#382](https://github.com/PagerDuty/go-pagerduty/pull/382) ([theckman](https://github.com/theckman)) +- Add omitempty JSON tag to User slice fields [\#383](https://github.com/PagerDuty/go-pagerduty/pull/383) ([theckman](https://github.com/theckman)) +- Add omitempty JSON tag to specific Service fields [\#384](https://github.com/PagerDuty/go-pagerduty/pull/384)([theckman](https://github.com/theckman)) +- Add support for adding email filters for Generic Email Integrations [\#385](https://github.com/PagerDuty/go-pagerduty/pull/385) ([theckman](https://github.com/theckman)) +- Support adding conference bridge when creating or managing incidents [\#391](https://github.com/PagerDuty/go-pagerduty/pull/391) ([theckman](https://github.com/theckman)) +- Mark Type struct field deprecated, for incident creation + management [\#392](https://github.com/PagerDuty/go-pagerduty/pull/392) ([theckman](https://github.com/theckman)) +- Fix some mismatches between REST API and struct definitions [\#396](https://github.com/PagerDuty/go-pagerduty/pull/396) ([theckman](https://github.com/theckman)) +- refactor: Reduce code duplication in analytics.go [\#397](https://github.com/PagerDuty/go-pagerduty/pull/397) ([t-junjie](https://github.com/t-junjie)) +- Add support for escalation_policy.on_call_handoff_notifications field [\#401](https://github.com/PagerDuty/go-pagerduty/pull/401) ([zonorti](https://github.com/zonorti)) +- Missing incident fields [\#402](https://github.com/PagerDuty/go-pagerduty/pull/402) ([zonorti](https://github.com/zonorti)) +- Add extension enable [\#403](https://github.com/PagerDuty/go-pagerduty/pull/403) ([zonorti](https://github.com/zonorti)) +- Add support for response_plays [\#404](https://github.com/PagerDuty/go-pagerduty/pull/404) ([petetanton](https://github.com/petetanton)) +- Update pagination query to conform to API spec [\#405](https://github.com/PagerDuty/go-pagerduty/pull/405) ([theckman](https://github.com/theckman)) +- add createStatusUpdate [\#406](https://github.com/PagerDuty/go-pagerduty/pull/406) ([kkawamura](https://github.com/kkawamura)) +- feat: Add support for fetching Audit Records [\#408](https://github.com/PagerDuty/go-pagerduty/pull/408) ([t-junjie](https://github.com/t-junjie)) +- Fix linter issues, update ResponsePlays API before v1.5.0 release [\#410](https://github.com/PagerDuty/go-pagerduty/pull/410) ([theckman](https://github.com/theckman)) +- Second batch of fixes for API incompatibilities [\#414](https://github.com/PagerDuty/go-pagerduty/pull/414) ([theckman](https://github.com/theckman)) +- Find a way to gracefully avoid one breaking change in #405 [\#416](https://github.com/PagerDuty/go-pagerduty/pull/416) ([theckman](https://github.com/theckman)) +- Fix linter issues introduced by final PR merges [\#417](https://github.com/PagerDuty/go-pagerduty/pull/417) ([theckman](https://github.com/theckman)) + +## [v1.4.3](https://github.com/PagerDuty/go-pagerduty/tree/v1.4.3) (2021-11-13) + +[Milestone](https://github.com/PagerDuty/go-pagerduty/milestone/6) +[Full Changelog](https://github.com/PagerDuty/go-pagerduty/compare/v1.4.2...v1.4.3) + +**Highlights** +- Mitigate PagerDuty REST API bug that would result in a JSON parsing failure when reading an error response from the API. Prior to `v1.4.0` our error parsing logic was not impacted by the bug. + +**Merged pull requests** +- Handle unexpected type changes in PagerDuty REST API error responses [\#382](https://github.com/PagerDuty/go-pagerduty/pull/382) [backported via [\#386](https://github.com/PagerDuty/go-pagerduty/pull/386)] ([theckman](https://github.com/theckman)) + +**Closed issues** +- APIError unmarshaling broken in 1.4 [\#339](https://github.com/PagerDuty/go-pagerduty/pull/339) ([dsymonds](https://github.com/dsymonds)) + +## [v1.4.2](https://github.com/PagerDuty/go-pagerduty/tree/v1.4.2) (2021-08-30) + +[Milestone](https://github.com/PagerDuty/go-pagerduty/milestone/5) +[Full Changelog](https://github.com/PagerDuty/go-pagerduty/compare/v1.4.1...v1.4.2) + +**Highlights** +- Fix bug that prevented CreateService() from working when SupportHours and ScheduledActions were unset. + +**Merged pull requests** +- Add omitempty on Service.SupportHours and Service.ScheduledActions [\#352](https://github.com/PagerDuty/go-pagerduty/pull/352) ([theckman](https://github.com/theckman)) + +**Closed issues** +- Backward incompatability - CreateService - existing setup works in 1.3.0 but not in 1.4.x [\#346](https://github.com/PagerDuty/go-pagerduty/pull/346) ([onikroo](https://github.com/onikroo)) + +## [v1.4.1](https://github.com/PagerDuty/go-pagerduty/tree/v1.4.1) (2021-05-13) + +[Milestone](https://github.com/PagerDuty/go-pagerduty/milestone/4) +[Full Changelog](https://github.com/PagerDuty/go-pagerduty/compare/v1.4.0...v1.4.1) + +**Highlights** +- Fix bugs that impacted pagination of both tags and business services + +**Merged pull requests** + +- Fix query params on tags [\#329](https://github.com/PagerDuty/go-pagerduty/pull/329) ([jfmyers9](https://github.com/jfmyers9)) +- Fix pagination within ListBusinessServices + Paginated [\#330](https://github.com/PagerDuty/go-pagerduty/pull/330) ([theckman](https://github.com/theckman)) + +## [v1.4.0](https://github.com/PagerDuty/go-pagerduty/tree/v1.4.0) (2021-04-23) + +[Milestone](https://github.com/PagerDuty/go-pagerduty/milestone/3) +[Full Changelog](https://github.com/PagerDuty/go-pagerduty/compare/v1.3.0...v1.4.0) + +**Highlights** + +- Add support for passing a `context.Context` to all methods in package (in non-breaking way) +- Add new `APIError` type, that allows for [richer inspection of errors returned from API](https://github.com/PagerDuty/go-pagerduty/tree/v1.4.0#api-error-responses). +- Add support for the V2 Events API, Analytics, and Change Events. +- Miscellaneous bug fixes, including some that may result in incorrect API request/response handling. + +**Closed issues** + +- CreateEventWithHTTPClient nil pointer dereference [\#274](https://github.com/PagerDuty/go-pagerduty/issues/274) ([blockpane](https://github.com/blockpane)) +- ManageIncidents - support set resolution [\#243](https://github.com/PagerDuty/go-pagerduty/issues/243) ([hagaishapira](https://github.com/hagaishapira)) +- Exposing context.Context in exported API without requiring major version bump [\#267](https://github.com/PagerDuty/go-pagerduty/issues/267) ([theckman](https://github.com/theckman/)) +- Fix any linter issues and add missing GoDoc comments [\#317](https://github.com/PagerDuty/go-pagerduty/issues/317) ([theckman](https://github.com/theckman)) + +**Merged pull requests** + +- Fix rulset rule not respecting position "0" [\#236](https://github.com/PagerDuty/go-pagerduty/pull/236) ([zane-deg](https://github.com/zane-deg)) +- Event v2 client [\#241](https://github.com/PagerDuty/go-pagerduty/pull/241) ([goatherder](https://github.com/goatherder)) +- Add Support For Change Events [\#246](https://github.com/PagerDuty/go-pagerduty/pull/246) ([Sjeanpierre](https://github.com/Sjeanpierre)) +- Correct namespacing of Change Event resource [\#248](https://github.com/PagerDuty/go-pagerduty/pull/248) ([Sjeanpierre](https://github.com/Sjeanpierre)) +- add tag and tag_test [\#252](https://github.com/PagerDuty/go-pagerduty/pull/252) ([stmcallister](https://github.com/stmcallister)) +- allow creating services with no scheduled actions [\#234](https://github.com/PagerDuty/go-pagerduty/pull/234) ([cluarkhpe](https://github.com/cluarkhpe)) +- business_service: clean b.ID before calling UPDATE [\#253](https://github.com/PagerDuty/go-pagerduty/pull/253) ([GiedriusS](https://github.com/GiedriusS)) +- service_dependency: fix (dis-)associate operations [\#254](https://github.com/PagerDuty/go-pagerduty/pull/254) ([GiedriusS](https://github.com/GiedriusS)) +- ruleset: remove omitempty from Route [\#256](https://github.com/PagerDuty/go-pagerduty/pull/256) ([GiedriusS](https://github.com/GiedriusS)) +- add service.alert_grouping_params field [\#257](https://github.com/PagerDuty/go-pagerduty/pull/257) ([stmcallister](https://github.com/stmcallister)) +- fix creating services that don't use support hours [\#255](https://github.com/PagerDuty/go-pagerduty/pull/255) ([cluarkhpe](https://github.com/cluarkhpe)) +- Provide a method for ferryign API errors back to the caller [#\265](https://github.com/PagerDuty/go-pagerduty/pull/265) ([theckman](https://github.com/theckman)) +- Prepare internals for exposing context.Context in exported API [#\266](https://github.com/PagerDuty/go-pagerduty/pull/266) ([theckman](https://github.com/theckman)) +- Update APIError struct to use new NullAPIErrorObject type for safety [#\272](https://github.com/PagerDuty/go-pagerduty/pull/272) ([theckman](https://github.com/theckman)) +- Update internal HTTP methods to not take pointer to map as argument [#\269](https://github.com/PagerDuty/go-pagerduty/pull/269) ([theckman](https://github.com/theckman)) +- Fix tags on log entry options struct [\#275](https://github.com/PagerDuty/go-pagerduty/pull/275) ([evnsio](https://github.com/evnsio)) +- Add alerts to IncidentDetails struct [\#276](https://github.com/PagerDuty/go-pagerduty/pull/276) ([StupidScience](https://github.com/StupidScience)) +- Fix logentry.channel json marshaling/unmarshaling [\#264](https://github.com/PagerDuty/go-pagerduty/pull/264) ([StupidScience](https://github.com/StupidScience)) +- Fixed CreateRuleSet docs typo [\#281](https://github.com/PagerDuty/go-pagerduty/pull/281) ([neufeldtech](https://github.com/neufeldtech)) +- Omitting EndpointURL from Extension if empty [\#282](https://github.com/PagerDuty/go-pagerduty/pull/282) ([au-akash](https://github.com/au-akash)) +- add ListServicesPaginated to services api (and tests) [\#260](https://github.com/PagerDuty/go-pagerduty/pull/260) ([c6h12o6](https://github.com/c6h12o6)) +- return nil instead of resp on error [\#278](https://github.com/PagerDuty/go-pagerduty/pull/278) (fixes [\#274](https://github.com/PagerDuty/go-pagerduty/issues/274)) ([blockpane](https://github.com/blockpane)) +- Analytics [\#261](https://github.com/PagerDuty/go-pagerduty/pull/261) ([melchiormoulin](https://github.com/melchiormoulin)) +- Add context.Context support to * (fixes [\#267](https://github.com/PagerDuty/go-pagerduty/issues/267)) ([theckman](https://github.com/theckman/)) + - [\#283](https://github.com/PagerDuty/go-pagerduty/pull/283), [\#284](https://github.com/PagerDuty/go-pagerduty/pull/284), [\#285](https://github.com/PagerDuty/go-pagerduty/pull/285), [\#286](https://github.com/PagerDuty/go-pagerduty/pull/286), [\#287](https://github.com/PagerDuty/go-pagerduty/pull/287), [\#288](https://github.com/PagerDuty/go-pagerduty/pull/288) + - [\#289](https://github.com/PagerDuty/go-pagerduty/pull/289), [\#290](https://github.com/PagerDuty/go-pagerduty/pull/290), [\#291](https://github.com/PagerDuty/go-pagerduty/pull/291), [\#292](https://github.com/PagerDuty/go-pagerduty/pull/292), [\#293](https://github.com/PagerDuty/go-pagerduty/pull/293), [\#294](https://github.com/PagerDuty/go-pagerduty/pull/294) + - [\#297](https://github.com/PagerDuty/go-pagerduty/pull/297), [\#298](https://github.com/PagerDuty/go-pagerduty/pull/298), [\#299](https://github.com/PagerDuty/go-pagerduty/pull/299), [\#300](https://github.com/PagerDuty/go-pagerduty/pull/300), [\#301](https://github.com/PagerDuty/go-pagerduty/pull/301), [\#303](https://github.com/PagerDuty/go-pagerduty/pull/303) + - [\#306](https://github.com/PagerDuty/go-pagerduty/pull/306), [\#307](https://github.com/PagerDuty/go-pagerduty/pull/307), [\#308](https://github.com/PagerDuty/go-pagerduty/pull/308), [\#309](https://github.com/PagerDuty/go-pagerduty/pull/309), [\#322](https://github.com/PagerDuty/go-pagerduty/pull/322) +- Add support for Service Event Rules [\#304](https://github.com/PagerDuty/go-pagerduty/pull/304) ([mrzacarias](https://github.com/mrzacarias)) +- teams and services analytics endpoints [\#312](https://github.com/PagerDuty/go-pagerduty/pull/312) ([newbootz](https://github.com/newbootz)) +- Add Resolution string to ManageIncidentsOptions struct [\#313](https://github.com/PagerDuty/go-pagerduty/pull/313) (fixes [\#243](https://github.com/PagerDuty/go-pagerduty/issues/243)) ([theckman](https://github.com/theckman/)) +- Fix invalid JSON struct tag, other linter issues [\#319](https://github.com/PagerDuty/go-pagerduty/pull/319) (fixes [\#317](https://github.com/PagerDuty/go-pagerduty/issues/317)) ([theckman](https://github.com/theckman)) +- Add ability to set Escalation Policy when managing incidents [\#323](https://github.com/PagerDuty/go-pagerduty/pull/323) ([theckman](https://github.com/theckman)) superseded: [\#273](https://github.com/PagerDuty/go-pagerduty/pull/273) ([evnsio](https://github.com/evnsio)) + + +## [v1.3.0](https://github.com/PagerDuty/go-pagerduty/tree/v1.3.0) (2020-09-08) + +[Full Changelog](https://github.com/PagerDuty/go-pagerduty/compare/v1.2.0...v1.3.0) + +**Closed issues:** + +- `ListIncidents` pagination [\#238](https://github.com/PagerDuty/go-pagerduty/issues/238) + +**Merged pull requests:** + +- Fix ruleset rule not respecting position "0" [\#236](https://github.com/PagerDuty/go-pagerduty/pull/236) ([zane-deg](https://github.com/zane-deg)) +- Adding Get Incident Alert and Manage Incident Alert endpoints [\#231](https://github.com/PagerDuty/go-pagerduty/pull/231) ([stmcallister](https://github.com/stmcallister)) +- Add FirstTriggerLogEntry and CommonLogEntryField fields and json tags [\#230](https://github.com/PagerDuty/go-pagerduty/pull/230) ([afarbos](https://github.com/afarbos)) +- adding business_service and service_dependency [\#228](https://github.com/PagerDuty/go-pagerduty/pull/228) ([stmcallister](https://github.com/stmcallister)) +- update changelog for v1.2.0 [\#227](https://github.com/PagerDuty/go-pagerduty/pull/227) ([stmcallister](https://github.com/stmcallister)) + + +## [v1.2.0](https://github.com/PagerDuty/go-pagerduty/tree/v1.2.0) (2020-06-04) + +[Full Changelog](https://github.com/PagerDuty/go-pagerduty/compare/v1.1.2...v1.2.0) + +**Closed issues:** + +- Allowing custom API endpoint in NewClient config [\#198](https://github.com/PagerDuty/go-pagerduty/issues/198) +- service: SupportHours creation not supported [\#188](https://github.com/PagerDuty/go-pagerduty/issues/188) +- The "Channel" field doesn't expose all possible data fields [\#153](https://github.com/PagerDuty/go-pagerduty/issues/153) + +**Merged pull requests:** + +- Adding Rulesets and Ruleset Rules [\#226](https://github.com/PagerDuty/go-pagerduty/pull/226) ([stmcallister](https://github.com/stmcallister)) +- Fix UpdateService [\#220](https://github.com/PagerDuty/go-pagerduty/pull/220) ([n-apalm](https://github.com/n-apalm)) +- Add support for modifying an incident status and assignees [\#219](https://github.com/PagerDuty/go-pagerduty/pull/219) ([raidancampbell](https://github.com/raidancampbell)) +- This should be requester\_id according to pagerduty docs [\#217](https://github.com/PagerDuty/go-pagerduty/pull/217) ([michael-bud](https://github.com/michael-bud)) +- adding since and until to incident logentry options [\#216](https://github.com/PagerDuty/go-pagerduty/pull/216) ([stmcallister](https://github.com/stmcallister)) +- User notification rules [\#215](https://github.com/PagerDuty/go-pagerduty/pull/215) ([heimweh](https://github.com/heimweh)) +- List incident alerts [\#214](https://github.com/PagerDuty/go-pagerduty/pull/214) ([kilianw](https://github.com/kilianw)) +- Bump golang to v1.14 [\#212](https://github.com/PagerDuty/go-pagerduty/pull/212) ([chenrui333](https://github.com/chenrui333)) +- adding NewClientWithAPIEndpoint function [\#210](https://github.com/PagerDuty/go-pagerduty/pull/210) ([stmcallister](https://github.com/stmcallister)) +- Webhook conforms to v2 struct [\#209](https://github.com/PagerDuty/go-pagerduty/pull/209) ([nbutton23](https://github.com/nbutton23)) +- Add Teams to Schedule [\#208](https://github.com/PagerDuty/go-pagerduty/pull/208) ([miekg](https://github.com/miekg)) +- adding Raw to LogEntry.Channel object [\#207](https://github.com/PagerDuty/go-pagerduty/pull/207) ([stmcallister](https://github.com/stmcallister)) +- Updating the Version constant to v1.1.2 [\#206](https://github.com/PagerDuty/go-pagerduty/pull/206) ([stmcallister](https://github.com/stmcallister)) +- updating changelog to v1.1.2 [\#205](https://github.com/PagerDuty/go-pagerduty/pull/205) ([stmcallister](https://github.com/stmcallister)) +- Adding OAuth token support [\#203](https://github.com/PagerDuty/go-pagerduty/pull/203) ([chrisforrette](https://github.com/chrisforrette)) + +## [v1.1.2](https://github.com/PagerDuty/go-pagerduty/tree/v1.1.2) (2020-02-21) + +[Full Changelog](https://github.com/PagerDuty/go-pagerduty/compare/1.1.1...v1.1.2) + +**Closed issues:** + +- EventV2Response doesn't match API response [\#186](https://github.com/PagerDuty/go-pagerduty/issues/186) +- List escalation policy with current on call members using include `current\_oncall` [\#181](https://github.com/PagerDuty/go-pagerduty/issues/181) +- Create service extension \(like slack extension\) over API [\#149](https://github.com/PagerDuty/go-pagerduty/issues/149) +- Mock Client? [\#148](https://github.com/PagerDuty/go-pagerduty/issues/148) +- Make a release? [\#146](https://github.com/PagerDuty/go-pagerduty/issues/146) +- Priority field should be optional according to API spec [\#135](https://github.com/PagerDuty/go-pagerduty/issues/135) +- Missing Services Extensions available over API [\#129](https://github.com/PagerDuty/go-pagerduty/issues/129) +- Missing ContactMethod operations [\#125](https://github.com/PagerDuty/go-pagerduty/issues/125) +- Add A CODEOWNERS file for easier review requests. [\#124](https://github.com/PagerDuty/go-pagerduty/issues/124) +- missing severity in create\_event.json object? [\#100](https://github.com/PagerDuty/go-pagerduty/issues/100) +- Assignment struct has no json conversion [\#92](https://github.com/PagerDuty/go-pagerduty/issues/92) +- Publish CLI binaries as releases [\#81](https://github.com/PagerDuty/go-pagerduty/issues/81) +- Package test coverage is lacking [\#70](https://github.com/PagerDuty/go-pagerduty/issues/70) +- Create releases with built binaries [\#50](https://github.com/PagerDuty/go-pagerduty/issues/50) + +**Merged pull requests:** + +- fixing eventV2Response to match API [\#204](https://github.com/PagerDuty/go-pagerduty/pull/204) ([stmcallister](https://github.com/stmcallister)) +- Remove duplicate license link in README [\#202](https://github.com/PagerDuty/go-pagerduty/pull/202) ([ahornace](https://github.com/ahornace)) +- Adding GetCurrentUser method [\#199](https://github.com/PagerDuty/go-pagerduty/pull/199) ([chrisforrette](https://github.com/chrisforrette)) +- Adding User-Agent Headers [\#197](https://github.com/PagerDuty/go-pagerduty/pull/197) ([stmcallister](https://github.com/stmcallister)) +- Implement the Incident endpoint for ResponderRequest [\#196](https://github.com/PagerDuty/go-pagerduty/pull/196) ([CerealBoy](https://github.com/CerealBoy)) +- updating changelog with 1.1.1 [\#195](https://github.com/PagerDuty/go-pagerduty/pull/195) ([stmcallister](https://github.com/stmcallister)) +- List team members, single page or all \(with helper for auto-pagination\) [\#192](https://github.com/PagerDuty/go-pagerduty/pull/192) ([mwhite-ibm](https://github.com/mwhite-ibm)) + ## [1.1.1](https://github.com/PagerDuty/go-pagerduty/tree/1.1.1) (2020-02-05) [Full Changelog](https://github.com/PagerDuty/go-pagerduty/compare/1.1.0...1.1.1) diff --git a/vendor/github.com/PagerDuty/go-pagerduty/Makefile b/vendor/github.com/PagerDuty/go-pagerduty/Makefile index 0387997..f93f4d4 100644 --- a/vendor/github.com/PagerDuty/go-pagerduty/Makefile +++ b/vendor/github.com/PagerDuty/go-pagerduty/Makefile @@ -9,24 +9,23 @@ # go build -o $(BINARY) command/* .PHONY: build - -GOPATH?=$(shell go env GOPATH) -GO111MODULE=auto - build: build-deps go build -mod=vendor -o pd ./command + +.PHONY: build-deps build-deps: go get go mod verify go mod vendor +.PHONY: install install: build - cp pd $(GOPATH)/bin + cp pd $(GOROOT)/bin .PHONY: test test: - go test ./... + go test -v ./... +.PHONY: deploy deploy: - curl -sL https://git.io/goreleaser | bash - diff --git a/vendor/github.com/PagerDuty/go-pagerduty/README.md b/vendor/github.com/PagerDuty/go-pagerduty/README.md index eebb7de..a59f46f 100644 --- a/vendor/github.com/PagerDuty/go-pagerduty/README.md +++ b/vendor/github.com/PagerDuty/go-pagerduty/README.md @@ -1,19 +1,18 @@ [![GoDoc](https://godoc.org/github.com/PagerDuty/go-pagerduty?status.svg)](http://godoc.org/github.com/PagerDuty/go-pagerduty) [![Go Report Card](https://goreportcard.com/badge/github.com/PagerDuty/go-pagerduty)](https://goreportcard.com/report/github.com/PagerDuty/go-pagerduty) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/gojp/goreportcard/blob/master/LICENSE) # go-pagerduty -go-pagerduty is a CLI and [go](https://golang.org/) client library for the [PagerDuty v2 API](https://v2.developer.pagerduty.com/v2/page/api-reference). +go-pagerduty is a CLI and [go](https://golang.org/) client library for the [PagerDuty API](https://developer.pagerduty.com/api-reference/). ## Installation -First, download the source code +To add the latest stable version to your project: ```cli -go get github.com/PagerDuty/go-pagerduty +go get github.com/PagerDuty/go-pagerduty@v1.8 ``` -Next build the application. +If you instead wish to work with the latest code from main: ```cli -cd $GOPATH/src/github.com/PagerDuty/go-pagerduty -make install +go get github.com/PagerDuty/go-pagerduty@latest ``` ## Usage @@ -42,9 +41,24 @@ An example of the `service` sub-command pd service list ``` - ### Client Library +#### NOTICE: Breaking API Changes in v1.5.0 + +As part of the `v1.5.0` release, we have fixed features that have never worked +correctly and require a breaking API change to fix. One example is the issue +reported in [\#232](https://github.com/PagerDuty/go-pagerduty/issues/232), as +well as a handful of other examples within the [v1.5.0 +milestone](https://github.com/PagerDuty/go-pagerduty/milestone/2). + +If you are impacted by a breaking change in this release, you should audit the +functionality you depended on as it may not have been working. If you cannot +upgrade for some reason, the `v1.4.x` line of releases should still work. At the +time of writing `v1.4.3` was the latest, and we intend to backport any critical +fixes for the time being. + +#### Example Usage + ```go package main @@ -56,9 +70,11 @@ import ( var authtoken = "" // Set your auth token here func main() { - var opts pagerduty.ListEscalationPoliciesOptions + ctx := context.Background() client := pagerduty.NewClient(authtoken) - eps, err := client.ListEscalationPolicies(opts) + + var opts pagerduty.ListEscalationPoliciesOptions + eps, err := client.ListEscalationPoliciesWithContext(ctx, opts) if err != nil { panic(err) } @@ -73,6 +89,102 @@ If you need to use your own HTTP client, for doing things like defining your own transport settings, you can replace the default HTTP client with your own by simply by setting a new value in the `HTTPClient` field. +#### API Error Responses + +For cases where your request results in an error from the API, you can use the +`errors.As()` function from the standard library to extract the +`pagerduty.APIError` error value and inspect more details about the error, +including the HTTP response code and PagerDuty API Error Code. + +```go +package main + +import ( + "fmt" + "github.com/PagerDuty/go-pagerduty" +) + +var authtoken = "" // Set your auth token here + +func main() { + ctx := context.Background() + client := pagerduty.NewClient(authtoken) + user, err := client.GetUserWithContext(ctx, "NOTREAL", pagerduty.GetUserOptions{}) + if err != nil { + var aerr pagerduty.APIError + + if errors.As(err, &aerr) { + if aerr.RateLimited() { + fmt.Println("rate limited") + return + } + + fmt.Println("unknown status code:", aerr.StatusCode) + + return + } + + panic(err) + } + fmt.Println(user) +} +``` + +#### Extending and Debugging Client + +##### Extending The Client + +The `*pagerduty.Client` has a `Do` method which allows consumers to wrap the +client, and make their own requests to the PagerDuty API. The method signature +is similar to that of the `http.Client.Do` method, except it also includes a +`bool` to incidate whether the API endpoint is authenticated (i.e., the REST +API). When the API is authenticated, the client will annotate the request with +the appropriate headers to be authenticated by the API. + +If the PagerDuty client doesn't natively expose functionality that you wish to +use, such as undocumented JSON fields, you can use the `Do()` method to issue +your own request that you can parse the response of. + +Likewise, you can use it to issue requests to the API for the purposes of +debugging. However, that's not the only mechanism for debugging. + +##### Debugging the Client + +The `*pagerduty.Client` has a method that allows consumers to enable debug +functionality, including interception of PagerDuty API responses. This is done +by using the `SetDebugFlag()` method using the `pagerduty.DebugFlag` unsigned +integer type. There are also exported constants to help consumers enable +specific debug behaviors. + +###### Capturing Last PagerDuty Response + +If you're not getting the response you expect from the PagerDuty Go client, you +can enable the `DebugCaptureLastResponse` debug flag to capture the HTTP +responses. You can then use one of the methods to make an API call, and then +inspect the API response received. For example: + +```Go +client := pagerduty.NewClient("example") + +client.SetDebugFlag(pagerduty.DebugCaptureLastResponse) + +oncalls, err := client.ListOnCallsWithContext(ctx, pagerduty.ListOnCallOptions{}) + +resp, ok := client.LastAPIResponse() +if ok { // resp is an *http.Response we can inspect + body, err := httputil.DumpResponse(resp, true) + // ... +} +``` + +#### Included Packages + +##### webhookv3 + +Support for V3 of PagerDuty Webhooks is provided via the `webhookv3` package. +The intent is for this package to provide signature verification and decoding +helpers. + ## Contributing 1. Fork it ( https://github.com/PagerDuty/go-pagerduty/fork ) diff --git a/vendor/github.com/PagerDuty/go-pagerduty/ability.go b/vendor/github.com/PagerDuty/go-pagerduty/ability.go index 26fe909..98646f4 100644 --- a/vendor/github.com/PagerDuty/go-pagerduty/ability.go +++ b/vendor/github.com/PagerDuty/go-pagerduty/ability.go @@ -1,22 +1,43 @@ package pagerduty +import "context" + // ListAbilityResponse is the response when calling the ListAbility API endpoint. type ListAbilityResponse struct { Abilities []string `json:"abilities"` } // ListAbilities lists all abilities on your account. +// +// Deprecated: Use ListAbilitiesWithContext instead. func (c *Client) ListAbilities() (*ListAbilityResponse, error) { - resp, err := c.get("/abilities") + return c.ListAbilitiesWithContext(context.Background()) +} + +// ListAbilitiesWithContext lists all abilities on your account. +func (c *Client) ListAbilitiesWithContext(ctx context.Context) (*ListAbilityResponse, error) { + resp, err := c.get(ctx, "/abilities", nil) if err != nil { return nil, err } + var result ListAbilityResponse - return &result, c.decodeJSON(resp, &result) + if err := c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil } -// TestAbility Check if your account has the given ability. +// TestAbility checks if your account has the given ability. +// +// Deprecated: Use TestAbilityWithContext instead. func (c *Client) TestAbility(ability string) error { - _, err := c.get("/abilities/" + ability) + return c.TestAbilityWithContext(context.Background(), ability) +} + +// TestAbilityWithContext checks if your account has the given ability. +func (c *Client) TestAbilityWithContext(ctx context.Context, ability string) error { + _, err := c.get(ctx, "/abilities/"+ability, nil) return err } diff --git a/vendor/github.com/PagerDuty/go-pagerduty/addon.go b/vendor/github.com/PagerDuty/go-pagerduty/addon.go index d5490f3..86f5240 100644 --- a/vendor/github.com/PagerDuty/go-pagerduty/addon.go +++ b/vendor/github.com/PagerDuty/go-pagerduty/addon.go @@ -1,6 +1,7 @@ package pagerduty import ( + "context" "fmt" "net/http" @@ -17,7 +18,25 @@ type Addon struct { // ListAddonOptions are the options available when calling the ListAddons API endpoint. type ListAddonOptions struct { - APIListObject + // Limit is the pagination parameter that limits the number of results per + // page. PagerDuty defaults this value to 25 if omitted, and sets an upper + // bound of 100. + Limit uint `url:"limit,omitempty"` + + // Offset is the pagination parameter that specifies the offset at which to + // start pagination results. When trying to request the next page of + // results, the new Offset value should be currentOffset + Limit. + Offset uint `url:"offset,omitempty"` + + // Total is the pagination parameter to request that the API return the + // total count of items in the response. If this field is omitted or set to + // false, the total number of results will not be sent back from the PagerDuty API. + // + // Setting this to true will slow down the API response times, and so it's + // recommended to omit it unless you've a specific reason for wanting the + // total count of items in the collection. + Total bool `url:"total,omitempty"` + Includes []string `url:"include,omitempty,brackets"` ServiceIDs []string `url:"service_ids,omitempty,brackets"` Filter string `url:"filter,omitempty"` @@ -30,57 +49,105 @@ type ListAddonResponse struct { } // ListAddons lists all of the add-ons installed on your account. +// +// Deprecated: Use ListAddonsWithContext instead. func (c *Client) ListAddons(o ListAddonOptions) (*ListAddonResponse, error) { + return c.ListAddonsWithContext(context.Background(), o) +} + +// ListAddonsWithContext lists all of the add-ons installed on your account. +func (c *Client) ListAddonsWithContext(ctx context.Context, o ListAddonOptions) (*ListAddonResponse, error) { v, err := query.Values(o) if err != nil { return nil, err } - resp, err := c.get("/addons?" + v.Encode()) + + resp, err := c.get(ctx, "/addons?"+v.Encode(), nil) if err != nil { return nil, err } + var result ListAddonResponse - return &result, c.decodeJSON(resp, &result) + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil } // InstallAddon installs an add-on for your account. +// +// Deprecated: Use InstallAddonWithContext instead. func (c *Client) InstallAddon(a Addon) (*Addon, error) { - data := make(map[string]Addon) - data["addon"] = a - resp, err := c.post("/addons", data, nil) - defer resp.Body.Close() + return c.InstallAddonWithContext(context.Background(), a) +} + +// InstallAddonWithContext installs an add-on for your account. +func (c *Client) InstallAddonWithContext(ctx context.Context, a Addon) (*Addon, error) { + d := map[string]Addon{ + "addon": a, + } + + resp, err := c.post(ctx, "/addons", d, nil) if err != nil { return nil, err } + if resp.StatusCode != http.StatusCreated { return nil, fmt.Errorf("Failed to create. HTTP Status code: %d", resp.StatusCode) } + return getAddonFromResponse(c, resp) } // DeleteAddon deletes an add-on from your account. +// +// Deprecated: Use DeleteAddonWithContext instead. func (c *Client) DeleteAddon(id string) error { - _, err := c.delete("/addons/" + id) + return c.DeleteAddonWithContext(context.Background(), id) +} + +// DeleteAddonWithContext deletes an add-on from your account. +func (c *Client) DeleteAddonWithContext(ctx context.Context, id string) error { + _, err := c.delete(ctx, "/addons/"+id) return err } // GetAddon gets details about an existing add-on. +// +// Deprecated: Use GetAddonWithContext instead. func (c *Client) GetAddon(id string) (*Addon, error) { - resp, err := c.get("/addons/" + id) + return c.GetAddonWithContext(context.Background(), id) +} + +// GetAddonWithContext gets details about an existing add-on. +func (c *Client) GetAddonWithContext(ctx context.Context, id string) (*Addon, error) { + resp, err := c.get(ctx, "/addons/"+id, nil) if err != nil { return nil, err } + return getAddonFromResponse(c, resp) } // UpdateAddon updates an existing add-on. +// +// Deprecated: Use UpdateAddonWithContext instead. func (c *Client) UpdateAddon(id string, a Addon) (*Addon, error) { - v := make(map[string]Addon) - v["addon"] = a - resp, err := c.put("/addons/"+id, v, nil) + return c.UpdateAddonWithContext(context.Background(), id, a) +} + +// UpdateAddonWithContext updates an existing add-on. +func (c *Client) UpdateAddonWithContext(ctx context.Context, id string, a Addon) (*Addon, error) { + d := map[string]Addon{ + "addon": a, + } + + resp, err := c.put(ctx, "/addons/"+id, d, nil) if err != nil { return nil, err } + return getAddonFromResponse(c, resp) } @@ -89,9 +156,13 @@ func getAddonFromResponse(c *Client, resp *http.Response) (*Addon, error) { if err := c.decodeJSON(resp, &result); err != nil { return nil, err } - a, ok := result["addon"] + + const rootNode = "addon" + + a, ok := result[rootNode] if !ok { - return nil, fmt.Errorf("JSON response does not have 'addon' field") + return nil, fmt.Errorf("JSON response does not have %s field", rootNode) } + return &a, nil } diff --git a/vendor/github.com/PagerDuty/go-pagerduty/analytics.go b/vendor/github.com/PagerDuty/go-pagerduty/analytics.go new file mode 100644 index 0000000..ea6ce15 --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/analytics.go @@ -0,0 +1,96 @@ +package pagerduty + +import ( + "context" + "fmt" +) + +const analyticsBaseURL = "/analytics/metrics/incidents" + +// AnalyticsRequest represents the request to be sent to PagerDuty when you want +// aggregated analytics. +type AnalyticsRequest struct { + Filters *AnalyticsFilter `json:"filters,omitempty"` + AggregateUnit string `json:"aggregate_unit,omitempty"` + TimeZone string `json:"time_zone,omitempty"` +} + +// AnalyticsResponse represents the response from the PagerDuty API. +type AnalyticsResponse struct { + Data []AnalyticsData `json:"data,omitempty"` + Filters *AnalyticsFilter `json:"filters,omitempty"` + AggregateUnit string `json:"aggregate_unit,omitempty"` + TimeZone string `json:"time_zone,omitempty"` +} + +// AnalyticsFilter represents the set of filters as part of the request to PagerDuty when +// requesting analytics. +type AnalyticsFilter struct { + CreatedAtStart string `json:"created_at_start,omitempty"` + CreatedAtEnd string `json:"created_at_end,omitempty"` + Urgency string `json:"urgency,omitempty"` + Major bool `json:"major,omitempty"` + ServiceIDs []string `json:"service_ids,omitempty"` + TeamIDs []string `json:"team_ids,omitempty"` + PriorityIDs []string `json:"priority_ids,omitempty"` + PriorityNames []string `json:"priority_names,omitempty"` +} + +// AnalyticsData represents the structure of the analytics we have available. +type AnalyticsData struct { + ServiceID string `json:"service_id,omitempty"` + ServiceName string `json:"service_name,omitempty"` + TeamID string `json:"team_id,omitempty"` + TeamName string `json:"team_name,omitempty"` + MeanSecondsToResolve int `json:"mean_seconds_to_resolve,omitempty"` + MeanSecondsToFirstAck int `json:"mean_seconds_to_first_ack,omitempty"` + MeanSecondsToEngage int `json:"mean_seconds_to_engage,omitempty"` + MeanSecondsToMobilize int `json:"mean_seconds_to_mobilize,omitempty"` + MeanEngagedSeconds int `json:"mean_engaged_seconds,omitempty"` + MeanEngagedUserCount int `json:"mean_engaged_user_count,omitempty"` + TotalEscalationCount int `json:"total_escalation_count,omitempty"` + MeanAssignmentCount int `json:"mean_assignment_count,omitempty"` + TotalBusinessHourInterruptions int `json:"total_business_hour_interruptions,omitempty"` + TotalSleepHourInterruptions int `json:"total_sleep_hour_interruptions,omitempty"` + TotalOffHourInterruptions int `json:"total_off_hour_interruptions,omitempty"` + TotalSnoozedSeconds int `json:"total_snoozed_seconds,omitempty"` + TotalEngagedSeconds int `json:"total_engaged_seconds,omitempty"` + TotalIncidentCount int `json:"total_incident_count,omitempty"` + UpTimePct float64 `json:"up_time_pct,omitempty"` + UserDefinedEffortSeconds int `json:"user_defined_effort_seconds,omitempty"` + RangeStart string `json:"range_start,omitempty"` +} + +// GetAggregatedIncidentData gets the aggregated incident analytics for the requested data. +func (c *Client) GetAggregatedIncidentData(ctx context.Context, analytics AnalyticsRequest) (AnalyticsResponse, error) { + return c.getAggregatedData(ctx, analytics, "all") +} + +// GetAggregatedServiceData gets the aggregated service analytics for the requested data. +func (c *Client) GetAggregatedServiceData(ctx context.Context, analytics AnalyticsRequest) (AnalyticsResponse, error) { + return c.getAggregatedData(ctx, analytics, "services") +} + +// GetAggregatedTeamData gets the aggregated team analytics for the requested data. +func (c *Client) GetAggregatedTeamData(ctx context.Context, analytics AnalyticsRequest) (AnalyticsResponse, error) { + return c.getAggregatedData(ctx, analytics, "teams") +} + +func (c *Client) getAggregatedData(ctx context.Context, analytics AnalyticsRequest, endpoint string) (AnalyticsResponse, error) { + h := map[string]string{ + "X-EARLY-ACCESS": "analytics-v2", + } + + u := fmt.Sprintf("%s/%s", analyticsBaseURL, endpoint) + resp, err := c.post(ctx, u, analytics, h) + if err != nil { + return AnalyticsResponse{}, err + } + + var analyticsResponse AnalyticsResponse + if err = c.decodeJSON(resp, &analyticsResponse); err != nil { + return AnalyticsResponse{}, err + } + + return analyticsResponse, nil +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/audit.go b/vendor/github.com/PagerDuty/go-pagerduty/audit.go new file mode 100644 index 0000000..744bf53 --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/audit.go @@ -0,0 +1,166 @@ +package pagerduty + +import ( + "context" + "fmt" + "net/http" + + "github.com/google/go-querystring/query" +) + +const auditBaseURL = "/audit/records" + +// ListAuditRecordsOptions is the data structure used when calling the +// ListAuditRecords API endpoint. +type ListAuditRecordsOptions struct { + Actions []string `url:"actions,omitempty,brackets"` + ActorID string `url:"actor_id,omitempty"` + ActorType string `url:"actor_type,omitempty"` + Cursor string `url:"cursor,omitempty"` + Limit uint `url:"limit,omitempty"` + MethodTruncatedToken string `url:"method_truncated_token,omitempty"` + MethodType string `url:"method_type,omitempty"` + RootResourcesTypes []string `url:"root_resources_types,omitempty,brackets"` + Since string `url:"since,omitempty"` + Until string `url:"until,omitempty"` +} + +// ListAuditRecordsResponse is the response data received when calling the +// ListAuditRecords API endpoint. +type ListAuditRecordsResponse struct { + Records []AuditRecord `json:"records,omitempty"` + // ResponseMetadata is not a required field in the pagerduty API response, + // using a pointer allows us to not marshall an empty ResponseMetaData struct + // into a JSON. + ResponseMetaData *ResponseMetadata `json:"response_metadata,omitempty"` + Limit uint `json:"limit,omitempty"` + // NextCursor is an opaque string that will deliver the next set of results + // when provided as the cursor parameter in a subsequent request. + // A null value for this field indicates that there are no additional results. + // We use a pointer here to marshall the string value into null + // when NextCursor is an empty string. + NextCursor *string `json:"next_cursor"` +} + +// AuditRecord is a audit trail record that matches the query criteria. +type AuditRecord struct { + ID string `json:"id,omitempty"` + Self string `json:"self,omitempty"` + ExecutionTime string `json:"execution_time,omitempty"` + ExecutionContext ExecutionContext `json:"execution_context,omitempty"` + Actors []APIObject `json:"actors,omitempty"` + Method Method `json:"method,omitempty"` + RootResource APIObject `json:"root_resource,omitempty"` + Action string `json:"action,omitempty"` + Details Details `json:"details,omitempty"` +} + +// ResponseMetadata contains information about the response. +type ResponseMetadata struct { + Messages []string `json:"messages,omitempty"` +} + +// ExecutionContext contains information about the action execution context. +type ExecutionContext struct { + RequestID string `json:"request_id,omitempty"` + RemoteAddress string `json:"remote_address,omitempty"` +} + +// Method contains information on the method used to perform the action. +type Method struct { + Description string `json:"description,omitempty"` + TruncatedToken string `json:"truncated_token,omitempty"` + Type string `json:"type,omitempty"` +} + +// Details contain additional information about the action or the resource +// that has been audited. +type Details struct { + Resource APIObject `json:"resource,omitempty"` + // A set of fields that have been affected. + // The fields that have not been affected MAY be returned. + Fields []Field `json:"fields,omitempty"` + // A set of references that have been affected. + References []Reference `json:"references,omitempty"` +} + +// Field contains information about the resource field that have been affected. +type Field struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Value string `json:"value,omitempty"` + BeforeValue string `json:"before_value,omitempty"` +} + +// Reference contains information about the reference that have been affected. +type Reference struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Added []APIObject `json:"added,omitempty"` + Removed []APIObject `json:"removed,omitempty"` +} + +// ListAuditRecords lists audit trial records matching provided query params +// or default criteria. +func (c *Client) ListAuditRecords(ctx context.Context, o ListAuditRecordsOptions) (ListAuditRecordsResponse, error) { + v, err := query.Values(o) + if err != nil { + return ListAuditRecordsResponse{}, err + } + + u := fmt.Sprintf("%s?%s", auditBaseURL, v.Encode()) + resp, err := c.get(ctx, u, nil) + if err != nil { + return ListAuditRecordsResponse{}, err + } + + var result ListAuditRecordsResponse + if err = c.decodeJSON(resp, &result); err != nil { + return ListAuditRecordsResponse{}, err + } + + return result, nil +} + +// ListAuditRecordsPaginated lists audit trial records matching provided query +// params or default criteria, processing paginated responses. The include +// function decides whether or not to include a specific AuditRecord in +// the final result. If the include function is nil, all audit records from +// the API are included by default. +func (c *Client) ListAuditRecordsPaginated(ctx context.Context, o ListAuditRecordsOptions, include func(AuditRecord) bool) ([]AuditRecord, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + + if include == nil { + include = func(AuditRecord) bool { return true } + } + + var records []AuditRecord + + responseHandler := func(response *http.Response) (cursor, error) { + var result ListAuditRecordsResponse + if err := c.decodeJSON(response, &result); err != nil { + return cursor{}, err + } + + for _, r := range result.Records { + if include(r) { + records = append(records, r) + } + } + + return cursor{ + Limit: result.Limit, + NextCursor: *result.NextCursor, + }, nil + } + + u := fmt.Sprintf("%s?%s", auditBaseURL, v.Encode()) + if err := c.cursorGet(ctx, u, responseHandler); err != nil { + return nil, err + } + + return records, nil +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/business_service.go b/vendor/github.com/PagerDuty/go-pagerduty/business_service.go new file mode 100644 index 0000000..368ce96 --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/business_service.go @@ -0,0 +1,200 @@ +package pagerduty + +import ( + "context" + "fmt" + "net/http" + + "github.com/google/go-querystring/query" +) + +// BusinessService represents a business service. +type BusinessService struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` + Summary string `json:"summary,omitempty"` + Self string `json:"self,omitempty"` + PointOfContact string `json:"point_of_contact,omitempty"` + HTMLUrl string `json:"html_url,omitempty"` + Description string `json:"description,omitempty"` + Team *BusinessServiceTeam `json:"team,omitempty"` +} + +// BusinessServiceTeam represents a team object in a business service +type BusinessServiceTeam struct { + ID string `json:"id,omitempty"` + Type string `json:"type,omitempty"` + Self string `json:"self,omitempty"` +} + +// BusinessServicePayload represents payload with a business service object +type BusinessServicePayload struct { + BusinessService *BusinessService `json:"business_service,omitempty"` +} + +// ListBusinessServicesResponse represents a list response of business services. +type ListBusinessServicesResponse struct { + Total uint `json:"total,omitempty"` + BusinessServices []*BusinessService `json:"business_services,omitempty"` + Offset uint `json:"offset,omitempty"` + More bool `json:"more,omitempty"` + Limit uint `json:"limit,omitempty"` +} + +// ListBusinessServiceOptions is the data structure used when calling the ListBusinessServices API endpoint. +type ListBusinessServiceOptions struct { + // Limit is the pagination parameter that limits the number of results per + // page. PagerDuty defaults this value to 25 if omitted, and sets an upper + // bound of 100. + Limit uint `url:"limit,omitempty"` + + // Offset is the pagination parameter that specifies the offset at which to + // start pagination results. When trying to request the next page of + // results, the new Offset value should be currentOffset + Limit. + Offset uint `url:"offset,omitempty"` + + // Total is the pagination parameter to request that the API return the + // total count of items in the response. If this field is omitted or set to + // false, the total number of results will not be sent back from the PagerDuty API. + // + // Setting this to true will slow down the API response times, and so it's + // recommended to omit it unless you've a specific reason for wanting the + // total count of items in the collection. + Total bool `url:"total,omitempty"` +} + +// ListBusinessServices lists existing business services. This method currently +// handles pagination of the response, so all business services should be +// present. +// +// Please note that the automatic pagination will be removed in v2 of this +// package, so it's recommended to use ListBusinessServicesPaginated instead. +func (c *Client) ListBusinessServices(o ListBusinessServiceOptions) (*ListBusinessServicesResponse, error) { + bss, err := c.ListBusinessServicesPaginated(context.Background(), o) + if err != nil { + return nil, err + } + + return &ListBusinessServicesResponse{BusinessServices: bss}, nil +} + +// ListBusinessServicesPaginated lists existing business services, automatically +// handling pagination and returning the full collection. +func (c *Client) ListBusinessServicesPaginated(ctx context.Context, o ListBusinessServiceOptions) ([]*BusinessService, error) { + queryParms, err := query.Values(o) + if err != nil { + return nil, err + } + + var businessServices []*BusinessService + + // Create a handler closure capable of parsing data from the business_services endpoint + // and appending resultant business_services to the return slice. + responseHandler := func(response *http.Response) (APIListObject, error) { + var result ListBusinessServicesResponse + if err := c.decodeJSON(response, &result); err != nil { + return APIListObject{}, err + } + + businessServices = append(businessServices, result.BusinessServices...) + + // Return stats on the current page. Caller can use this information to + // adjust for requesting additional pages. + return APIListObject{ + More: result.More, + Offset: result.Offset, + Limit: result.Limit, + }, nil + } + + // Make call to get all pages associated with the base endpoint. + if err := c.pagedGet(ctx, "/business_services?"+queryParms.Encode(), responseHandler); err != nil { + return nil, err + } + + return businessServices, nil +} + +// CreateBusinessService creates a new business service. +// +// Deprecated: Use CreateBusinessServiceWithContext instead +func (c *Client) CreateBusinessService(b *BusinessService) (*BusinessService, error) { + return c.CreateBusinessServiceWithContext(context.Background(), b) +} + +// CreateBusinessServiceWithContext creates a new business service. +func (c *Client) CreateBusinessServiceWithContext(ctx context.Context, b *BusinessService) (*BusinessService, error) { + d := map[string]*BusinessService{ + "business_service": b, + } + + resp, err := c.post(ctx, "/business_services", d, nil) + return getBusinessServiceFromResponse(resp, err, c.decodeJSON) +} + +// GetBusinessService gets details about a business service. +// +// Deprecated: Use GetBusinessServiceWithContext instead. +func (c *Client) GetBusinessService(id string) (*BusinessService, error) { + return c.GetBusinessServiceWithContext(context.Background(), id) +} + +// GetBusinessServiceWithContext gets details about a business service. +func (c *Client) GetBusinessServiceWithContext(ctx context.Context, id string) (*BusinessService, error) { + resp, err := c.get(ctx, "/business_services/"+id, nil) + return getBusinessServiceFromResponse(resp, err, c.decodeJSON) +} + +// DeleteBusinessService deletes a business_service. +// +// Deprecated: Use DeleteBusinessServiceWithContext instead. +func (c *Client) DeleteBusinessService(id string) error { + return c.DeleteBusinessServiceWithContext(context.Background(), id) +} + +// DeleteBusinessServiceWithContext deletes a business_service. +func (c *Client) DeleteBusinessServiceWithContext(ctx context.Context, id string) error { + _, err := c.delete(ctx, "/business_services/"+id) + return err +} + +// UpdateBusinessService updates a business_service. +// +// Deprecated: Use UpdateBusinessServiceWithContext instead. +func (c *Client) UpdateBusinessService(b *BusinessService) (*BusinessService, error) { + return c.UpdateBusinessServiceWithContext(context.Background(), b) +} + +// UpdateBusinessServiceWithContext updates a business_service. +func (c *Client) UpdateBusinessServiceWithContext(ctx context.Context, b *BusinessService) (*BusinessService, error) { + id := b.ID + b.ID = "" + + d := map[string]*BusinessService{ + "business_service": b, + } + + resp, err := c.put(ctx, "/business_services/"+id, d, nil) + return getBusinessServiceFromResponse(resp, err, c.decodeJSON) +} + +func getBusinessServiceFromResponse(resp *http.Response, err error, decodeFn func(*http.Response, interface{}) error) (*BusinessService, error) { + if err != nil { + return nil, err + } + + var target map[string]BusinessService + if dErr := decodeFn(resp, &target); dErr != nil { + return nil, fmt.Errorf("Could not decode JSON response: %v", dErr) + } + + const rootNode = "business_service" + + t, nodeOK := target[rootNode] + if !nodeOK { + return nil, fmt.Errorf("JSON response does not have %s field", rootNode) + } + + return &t, nil +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/change_events.go b/vendor/github.com/PagerDuty/go-pagerduty/change_events.go new file mode 100644 index 0000000..3dcf97d --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/change_events.go @@ -0,0 +1,86 @@ +package pagerduty + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "net/http" +) + +const changeEventPath = "/v2/change/enqueue" + +// ChangeEvent represents a ChangeEvent's request parameters +// https://developer.pagerduty.com/docs/events-api-v2/send-change-events/#parameters +type ChangeEvent struct { + RoutingKey string `json:"routing_key"` + Payload ChangeEventPayload `json:"payload"` + Links []ChangeEventLink `json:"links,omitempty"` +} + +// ChangeEventPayload ChangeEvent ChangeEventPayload +// https://developer.pagerduty.com/docs/events-api-v2/send-change-events/#example-request-payload +type ChangeEventPayload struct { + Summary string `json:"summary"` + Source string `json:"source,omitempty"` + Timestamp string `json:"timestamp,omitempty"` + CustomDetails map[string]interface{} `json:"custom_details,omitempty"` +} + +// ChangeEventLink represents a single link in a ChangeEvent +// https://developer.pagerduty.com/docs/events-api-v2/send-change-events/#the-links-property +type ChangeEventLink struct { + Href string `json:"href"` + Text string `json:"text,omitempty"` +} + +// ChangeEventResponse is the json response body for an event +type ChangeEventResponse struct { + Status string `json:"status,omitempty"` + Message string `json:"message,omitempty"` + Errors []string `json:"errors,omitempty"` +} + +// CreateChangeEvent Sends PagerDuty a single ChangeEvent to record +// The v2EventsAPIEndpoint parameter must be set on the client +// Documentation can be found at https://developer.pagerduty.com/docs/events-api-v2/send-change-events +// +// Deprecated: Use CreateChangeEventWithContext instead. +func (c *Client) CreateChangeEvent(e ChangeEvent) (*ChangeEventResponse, error) { + return c.CreateChangeEventWithContext(context.Background(), e) +} + +// CreateChangeEventWithContext sends PagerDuty a single ChangeEvent to record +// The v2EventsAPIEndpoint parameter must be set on the client Documentation can +// be found at https://developer.pagerduty.com/docs/events-api-v2/send-change-events +func (c *Client) CreateChangeEventWithContext(ctx context.Context, e ChangeEvent) (*ChangeEventResponse, error) { + if c.v2EventsAPIEndpoint == "" { + return nil, errors.New("v2EventsAPIEndpoint field must be set on Client") + } + + data, err := json.Marshal(e) + if err != nil { + return nil, err + } + + resp, err := c.doWithEndpoint( + ctx, + c.v2EventsAPIEndpoint, + http.MethodPost, + changeEventPath, + false, + bytes.NewBuffer(data), + nil, + ) + if err != nil { + return nil, err + } + + var eventResponse ChangeEventResponse + + if err := c.decodeJSON(resp, &eventResponse); err != nil { + return nil, err + } + + return &eventResponse, nil +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/client.go b/vendor/github.com/PagerDuty/go-pagerduty/client.go index 2b56893..6d65e05 100644 --- a/vendor/github.com/PagerDuty/go-pagerduty/client.go +++ b/vendor/github.com/PagerDuty/go-pagerduty/client.go @@ -1,22 +1,50 @@ +// Package pagerduty is a Go API client for both the PagerDuty v2 REST and +// Events API. Most methods should be implemented, and it's recommended to use +// the WithContext variant of each method and to specify a context with a +// timeout. +// +// To debug responses from the API, you can instruct the client to capture the +// last response from the API. Please see the documentation for the +// SetDebugFlag() and LastAPIResponse() methods for more details. package pagerduty import ( "bytes" + "context" "encoding/json" "fmt" "io" + "io/ioutil" "net" "net/http" + "path" "runtime" + "strings" + "sync/atomic" "time" ) +// Version is current version of this client. +const Version = "1.8.0" + +const ( + apiEndpoint = "https://api.pagerduty.com" + v2EventsAPIEndpoint = "https://events.pagerduty.com" +) + +// The type of authentication to use with the API client +type authType int + const ( - apiEndpoint = "https://api.pagerduty.com" + // Account/user API token authentication + apiToken authType = iota + + // OAuth token authentication + oauthToken ) // APIObject represents generic api json response that is shared by most -// domain object (like escalation +// domain objects (like escalation) type APIObject struct { ID string `json:"id,omitempty"` Type string `json:"type,omitempty"` @@ -27,10 +55,10 @@ type APIObject struct { // APIListObject are the fields used to control pagination when listing objects. type APIListObject struct { - Limit uint `url:"limit,omitempty"` - Offset uint `url:"offset,omitempty"` - More bool `url:"more,omitempty"` - Total uint `url:"total,omitempty"` + Limit uint `json:"limit,omitempty"` + Offset uint `json:"offset,omitempty"` + More bool `json:"more,omitempty"` + Total uint `json:"total,omitempty"` } // APIReference are the fields required to reference another API object. @@ -39,15 +67,172 @@ type APIReference struct { Type string `json:"type,omitempty"` } +// APIDetails are the fields required to represent a details non-hydrated +// object. type APIDetails struct { Type string `json:"type,omitempty"` Details string `json:"details,omitempty"` } -type errorObject struct { - Code int `json:"code,omitempty"` - Message string `json:"message,omitempty"` - Errors interface{} `json:"errors,omitempty"` +// APIErrorObject represents the object returned by the API when an error +// occurs. This includes messages that should hopefully provide useful context +// to the end user. +type APIErrorObject struct { + Code int `json:"code,omitempty"` + Message string `json:"message,omitempty"` + Errors []string `json:"errors,omitempty"` +} + +func unmarshalApiErrorObject(data []byte) (APIErrorObject, error) { + var aeo APIErrorObject + err := json.Unmarshal(data, &aeo) + if err == nil { + return aeo, nil + } + if _, ok := err.(*json.UnmarshalTypeError); !ok { + return aeo, nil + } + // See - https://github.com/PagerDuty/go-pagerduty/issues/339 + // TODO: remove when PagerDuty engineering confirms bugfix to the REST API + var fallback1 struct { + Code int `json:"code,omitempty"` + Message string `json:"message,omitempty"` + Errors string `json:"errors,omitempty"` + } + if json.Unmarshal(data, &fallback1) == nil { + aeo.Code = fallback1.Code + aeo.Message = fallback1.Message + aeo.Errors = []string{fallback1.Errors} + return aeo, nil + } + // See - https://github.com/PagerDuty/go-pagerduty/issues/478 + var fallback2 []string + if json.Unmarshal(data, &fallback2) == nil { + aeo.Message = "none" + aeo.Errors = fallback2 + return aeo, nil + } + // still failed, so return the original error + return aeo, err +} + +// NullAPIErrorObject is a wrapper around the APIErrorObject type. If the Valid +// field is true, the API response included a structured error JSON object. This +// structured object is then set on the ErrorObject field. +// +// While the PagerDuty REST API is documented to always return the error object, +// we assume it's possible in exceptional failure modes for this to be omitted. +// As such, this wrapper type provides us a way to check if the object was +// provided while avoiding consumers accidentally missing a nil pointer check, +// thus crashing their whole program. +type NullAPIErrorObject struct { + Valid bool + ErrorObject APIErrorObject +} + +var _ json.Unmarshaler = (*NullAPIErrorObject)(nil) // assert that it satisfies the json.Unmarshaler interface. + +// UnmarshalJSON satisfies encoding/json.Unmarshaler +func (n *NullAPIErrorObject) UnmarshalJSON(data []byte) error { + aeo, err := unmarshalApiErrorObject(data) + if err != nil { + return err + } + + n.ErrorObject = aeo + n.Valid = true + + return nil +} + +// APIError represents the error response received when an API call fails. The +// HTTP response code is set inside of the StatusCode field, with the APIError +// field being the structured JSON error object returned from the API. +// +// This type also provides some helper methods like .RateLimited(), .NotFound(), +// and .Temporary() to help callers reason about how to handle the error. +// +// You can read more about the HTTP status codes and API error codes returned +// from the API here: https://developer.pagerduty.com/docs/rest-api-v2/errors/ +type APIError struct { + // StatusCode is the HTTP response status code + StatusCode int `json:"-"` + + // APIError represents the object returned by the API when an error occurs, + // which includes messages that should hopefully provide useful context + // to the end user. + // + // If the API response did not contain an error object, the .Valid field of + // APIError will be false. If .Valid is true, the .ErrorObject field is + // valid and should be consulted. + APIError NullAPIErrorObject `json:"error"` + + message string +} + +// Error satisfies the error interface, and should contain the StatusCode, +// APIErrorObject.Message, and APIErrorObject.Code. +func (a APIError) Error() string { + if len(a.message) > 0 { + return a.message + } + + if !a.APIError.Valid { + return fmt.Sprintf("HTTP response failed with status code %d and no JSON error object was present", a.StatusCode) + } + + if len(a.APIError.ErrorObject.Errors) == 0 { + return fmt.Sprintf( + "HTTP response failed with status code %d, message: %s (code: %d)", + a.StatusCode, a.APIError.ErrorObject.Message, a.APIError.ErrorObject.Code, + ) + } + + return fmt.Sprintf( + "HTTP response failed with status code %d, message: %s (code: %d): %s", + a.StatusCode, + a.APIError.ErrorObject.Message, + a.APIError.ErrorObject.Code, + apiErrorsDetailString(a.APIError.ErrorObject.Errors), + ) +} + +func apiErrorsDetailString(errs []string) string { + switch n := len(errs); n { + case 0: + panic("errs slice is empty") + + case 1: + return errs[0] + + default: + e := "error" + if n > 2 { + e += "s" + } + + return fmt.Sprintf("%s (and %d more %s...)", errs[0], n-1, e) + } +} + +// RateLimited returns whether the response had a status of 429, and as such the +// client is rate limited. The PagerDuty rate limits should reset once per +// minute, and for the REST API they are an account-wide rate limit (not per +// API key or IP). +func (a APIError) RateLimited() bool { + return a.StatusCode == http.StatusTooManyRequests +} + +// Temporary returns whether it was a temporary error, one of which is a +// RateLimited error. +func (a APIError) Temporary() bool { + return a.RateLimited() || (a.StatusCode >= 500 && a.StatusCode < 600) +} + +// NotFound returns whether this was an error where it seems like the resource +// was not found. +func (a APIError) NotFound() bool { + return a.StatusCode == http.StatusNotFound || (a.APIError.Valid && a.APIError.ErrorObject.Code == 2100) } func newDefaultHTTPClient() *http.Client { @@ -85,100 +270,381 @@ var defaultHTTPClient HTTPClient = newDefaultHTTPClient() // Client wraps http client type Client struct { - authToken string - apiEndpoint string + debugFlag *uint64 + lastRequest *atomic.Value + lastResponse *atomic.Value + + authToken string + apiEndpoint string + v2EventsAPIEndpoint string + + // Authentication type to use for API + authType authType // HTTPClient is the HTTP client used for making requests against the // PagerDuty API. You can use either *http.Client here, or your own // implementation. HTTPClient HTTPClient + + userAgent string +} + +// NewClient creates an API client using an account/user API token +func NewClient(authToken string, options ...ClientOptions) *Client { + client := Client{ + debugFlag: new(uint64), + lastRequest: &atomic.Value{}, + lastResponse: &atomic.Value{}, + authToken: authToken, + apiEndpoint: apiEndpoint, + v2EventsAPIEndpoint: v2EventsAPIEndpoint, + authType: apiToken, + HTTPClient: defaultHTTPClient, + } + + for _, opt := range options { + opt(&client) + } + + return &client +} + +// NewOAuthClient creates an API client using an OAuth token +func NewOAuthClient(authToken string, options ...ClientOptions) *Client { + return NewClient(authToken, WithOAuth()) +} + +// ClientOptions allows for options to be passed into the Client for customization +type ClientOptions func(*Client) + +// WithAPIEndpoint allows for a custom API endpoint to be passed into the the client +func WithAPIEndpoint(endpoint string) ClientOptions { + return func(c *Client) { + c.apiEndpoint = endpoint + } +} + +// WithTerraformProvider configures the client to be used as the PagerDuty +// Terraform provider +func WithTerraformProvider(version string) ClientOptions { + return func(c *Client) { + c.userAgent = fmt.Sprintf("(%s %s) Terraform/%s", runtime.GOOS, runtime.GOARCH, version) + } +} + +// WithV2EventsAPIEndpoint allows for a custom V2 Events API endpoint to be passed into the client +func WithV2EventsAPIEndpoint(endpoint string) ClientOptions { + return func(c *Client) { + c.v2EventsAPIEndpoint = endpoint + } +} + +// WithOAuth allows for an OAuth token to be passed into the the client +func WithOAuth() ClientOptions { + return func(c *Client) { + c.authType = oauthToken + } +} + +// DebugFlag represents a set of debug bit flags that can be bitwise-ORed +// together to configure the different behaviors. This allows us to expand +// functionality in the future without introducing breaking changes. +type DebugFlag uint64 + +const ( + // DebugDisabled disables all debug behaviors. + DebugDisabled DebugFlag = 0 + + // DebugCaptureLastRequest captures the last HTTP request made to the API + // (if there was one) and makes it available via the LastAPIRequest() + // method. + // + // This may increase memory usage / GC, as we'll be making a copy of the + // full HTTP request body on each request and capturing it for inspection. + DebugCaptureLastRequest DebugFlag = 1 << 0 + + // DebugCaptureLastResponse captures the last HTTP response from the API (if + // there was one) and makes it available via the LastAPIResponse() method. + // + // This may increase memory usage / GC, as we'll be making a copy of the + // full HTTP response body on each request and capturing it for inspection. + DebugCaptureLastResponse DebugFlag = 1 << 1 +) + +// SetDebugFlag sets the DebugFlag of the client, which are just bit flags that +// tell the client how to behave. They can be bitwise-ORed together to enable +// multiple behaviors. +func (c *Client) SetDebugFlag(flag DebugFlag) { + atomic.StoreUint64(c.debugFlag, uint64(flag)) +} + +func (c *Client) debugCaptureRequest() bool { + return atomic.LoadUint64(c.debugFlag)&uint64(DebugCaptureLastRequest) > 0 +} + +func (c *Client) debugCaptureResponse() bool { + return atomic.LoadUint64(c.debugFlag)&uint64(DebugCaptureLastResponse) > 0 +} + +// LastAPIRequest returns the last request sent to the API, if enabled. This can +// be turned on by using the SetDebugFlag() method while providing the +// DebugCaptureLastRequest flag. +// +// The bool value returned from this method is false if the request is unset or +// is nil. If there was an error prepping the request to be sent to the server, +// there will be no *http.Request to capture so this will return (, false). +// +// This is meant to help with debugging unexpected API interactions, so most +// won't need to use it. Also, callers will need to ensure the *Client isn't +// used concurrently, otherwise they might receive another method's *http.Request +// and not the one they anticipated. +// +// The *http.Request made within the Do() method is not captured by the client, +// and thus won't be returned by this method. +func (c *Client) LastAPIRequest() (*http.Request, bool) { + v := c.lastRequest.Load() + if v == nil { + return nil, false + } + + // comma ok idiom omitted, if this is something else explode + r := v.(*http.Request) + if r == nil { + return nil, false + } + + return r, true } -// NewClient creates an API client -func NewClient(authToken string) *Client { - return &Client{ - authToken: authToken, - apiEndpoint: apiEndpoint, - HTTPClient: defaultHTTPClient, +// LastAPIResponse returns the last response received from the API, if enabled. +// This can be turned on by using the SetDebugFlag() method while providing the +// DebugCaptureLastResponse flag. +// +// The bool value returned from this method is false if the response is unset or +// is nil. If the HTTP exchange failed (e.g., there was a connection error) +// there will be no *http.Response to capture so this will return (, +// false). +// +// This is meant to help with debugging unexpected API interactions, so most +// won't need to use it. Also, callers will need to ensure the *Client isn't +// used concurrently, otherwise they might receive another method's *http.Response +// and not the one they anticipated. +// +// The *http.Response from the Do() method is not captured by the client, and thus +// won't be returned by this method. +func (c *Client) LastAPIResponse() (*http.Response, bool) { + v := c.lastResponse.Load() + if v == nil { + return nil, false } + + // comma ok idiom omitted, if this is something else explode + r := v.(*http.Response) + if r == nil { + return nil, false + } + + return r, true } -func (c *Client) delete(path string) (*http.Response, error) { - return c.do("DELETE", path, nil, nil) +// Do sets some headers on the request, before actioning it using the internal +// HTTPClient. If the PagerDuty API you're communicating with requires +// authentication, such as the REST API, set authRequired to true and the client +// will set the proper authentication headers onto the request. This also +// assumes any request body is in JSON format and sets the Content-Type to +// application/json. +func (c *Client) Do(r *http.Request, authRequired bool) (*http.Response, error) { + c.prepRequest(r, authRequired, nil) + + return c.HTTPClient.Do(r) } -func (c *Client) put(path string, payload interface{}, headers *map[string]string) (*http.Response, error) { +func (c *Client) delete(ctx context.Context, path string) (*http.Response, error) { + return c.do(ctx, http.MethodDelete, path, nil, nil) +} +func (c *Client) put(ctx context.Context, path string, payload interface{}, headers map[string]string) (*http.Response, error) { if payload != nil { data, err := json.Marshal(payload) if err != nil { return nil, err } - return c.do("PUT", path, bytes.NewBuffer(data), headers) + return c.do(ctx, http.MethodPut, path, bytes.NewBuffer(data), headers) } - return c.do("PUT", path, nil, headers) + return c.do(ctx, http.MethodPut, path, nil, headers) } -func (c *Client) post(path string, payload interface{}, headers *map[string]string) (*http.Response, error) { +func (c *Client) post(ctx context.Context, path string, payload interface{}, headers map[string]string) (*http.Response, error) { data, err := json.Marshal(payload) if err != nil { return nil, err } - return c.do("POST", path, bytes.NewBuffer(data), headers) + return c.do(ctx, http.MethodPost, path, bytes.NewBuffer(data), headers) +} + +func (c *Client) get(ctx context.Context, path string, headers map[string]string) (*http.Response, error) { + return c.do(ctx, http.MethodGet, path, nil, headers) +} + +const ( + userAgentHeader = "go-pagerduty/" + Version + acceptHeader = "application/vnd.pagerduty+json;version=2" + contentTypeHeader = "application/json" +) + +func (c *Client) prepRequest(req *http.Request, authRequired bool, headers map[string]string) { + req.Header.Set("Accept", acceptHeader) + + for k, v := range headers { + req.Header.Set(k, v) + } + + if authRequired { + switch c.authType { + case oauthToken: + req.Header.Set("Authorization", "Bearer "+c.authToken) + default: + req.Header.Set("Authorization", "Token token="+c.authToken) + } + } + + var userAgent string + if c.userAgent != "" { + userAgent = c.userAgent + } else { + userAgent = userAgentHeader + } + req.Header.Set("User-Agent", userAgent) + req.Header.Set("Content-Type", contentTypeHeader) } -func (c *Client) get(path string) (*http.Response, error) { - return c.do("GET", path, nil, nil) +func dupeRequest(r *http.Request) (*http.Request, error) { + dreq := r.Clone(r.Context()) + + if r.Body != nil { + data, err := ioutil.ReadAll(r.Body) + if err != nil { + return nil, fmt.Errorf("failed to copy request body: %w", err) + } + + _ = r.Body.Close() + + r.Body = ioutil.NopCloser(bytes.NewReader(data)) + dreq.Body = ioutil.NopCloser(bytes.NewReader(data)) + } + + return dreq, nil } -func (c *Client) do(method, path string, body io.Reader, headers *map[string]string) (*http.Response, error) { - endpoint := c.apiEndpoint + path - req, _ := http.NewRequest(method, endpoint, body) - req.Header.Set("Accept", "application/vnd.pagerduty+json;version=2") - if headers != nil { - for k, v := range *headers { - req.Header.Set(k, v) +// needed where pagerduty use a different endpoint for certain actions (eg: v2 events) +func (c *Client) doWithEndpoint(ctx context.Context, endpoint, method, path string, authRequired bool, body io.Reader, headers map[string]string) (*http.Response, error) { + var dreq *http.Request + var resp *http.Response + + // so that the last request and response can be nil if there was an error + // before the request could be fully processed by the origin, we defer these + // calls here + if c.debugCaptureResponse() { + defer func() { + c.lastResponse.Store(resp) + }() + } + + if c.debugCaptureRequest() { + defer func() { + c.lastRequest.Store(dreq) + }() + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint+path, body) + if err != nil { + return nil, fmt.Errorf("failed to build request: %w", err) + } + + c.prepRequest(req, authRequired, headers) + + // if in debug mode, copy request before making it + if c.debugCaptureRequest() { + if dreq, err = dupeRequest(req); err != nil { + return nil, fmt.Errorf("failed to duplicate request for debug capture: %w", err) } } - req.Header.Set("User-Agent", "go-pagerduty/"+Version) - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Token token="+c.authToken) - resp, err := c.HTTPClient.Do(req) + resp, err = c.HTTPClient.Do(req) + return c.checkResponse(resp, err) } +func (c *Client) do(ctx context.Context, method, path string, body io.Reader, headers map[string]string) (*http.Response, error) { + return c.doWithEndpoint(ctx, c.apiEndpoint, method, path, true, body, headers) +} + func (c *Client) decodeJSON(resp *http.Response, payload interface{}) error { - defer resp.Body.Close() - decoder := json.NewDecoder(resp.Body) - return decoder.Decode(payload) + // close the original response body, and not the copy we may make if + // debugCaptureResponse is true + orb := resp.Body + defer func() { _ = orb.Close() }() // explicitly discard error + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("failed to read response body: %w", err) + } + + if c.debugCaptureResponse() { // reset body as we capture the response elsewhere + resp.Body = ioutil.NopCloser(bytes.NewReader(body)) + } + + return json.Unmarshal(body, payload) } func (c *Client) checkResponse(resp *http.Response, err error) (*http.Response, error) { if err != nil { - return resp, fmt.Errorf("Error calling the API endpoint: %v", err) + return resp, fmt.Errorf("error calling the API endpoint: %v", err) } - if 199 >= resp.StatusCode || 300 <= resp.StatusCode { - var eo *errorObject - var getErr error - if eo, getErr = c.getErrorFromResponse(resp); getErr != nil { - return resp, fmt.Errorf("Response did not contain formatted error: %s. HTTP response code: %v. Raw response: %+v", getErr, resp.StatusCode, resp) - } - return resp, fmt.Errorf("Failed call API endpoint. HTTP response code: %v. Error: %v", resp.StatusCode, eo) + + if resp.StatusCode < 200 || resp.StatusCode > 299 { + return resp, c.getErrorFromResponse(resp) } + return resp, nil } -func (c *Client) getErrorFromResponse(resp *http.Response) (*errorObject, error) { - var result map[string]errorObject - if err := c.decodeJSON(resp, &result); err != nil { - return nil, fmt.Errorf("Could not decode JSON response: %v", err) +func (c *Client) getErrorFromResponse(resp *http.Response) APIError { + // check whether the error response is declared as JSON + if !strings.HasPrefix(resp.Header.Get("Content-Type"), "application/json") { + defer resp.Body.Close() + + aerr := APIError{ + StatusCode: resp.StatusCode, + message: fmt.Sprintf("HTTP response with status code %d does not contain Content-Type: application/json", resp.StatusCode), + } + + return aerr } - s, ok := result["error"] - if !ok { - return nil, fmt.Errorf("JSON response does not have error field") + + var document APIError + + // because of above check this probably won't fail, but it's possible... + if err := c.decodeJSON(resp, &document); err != nil { + aerr := APIError{ + StatusCode: resp.StatusCode, + message: fmt.Sprintf("HTTP response with status code %d, JSON error object decode failed: %s", resp.StatusCode, err), + } + + return aerr } - return &s, nil + + document.StatusCode = resp.StatusCode + + return document +} + +// Helper function to determine wither additional parameters should use ? or & to append args +func getBasePrefix(basePath string) string { + if strings.Contains(path.Base(basePath), "?") { + return basePath + "&" + } + return basePath + "?" } // responseHandler is capable of parsing a response. At a minimum it must @@ -188,16 +654,17 @@ func (c *Client) getErrorFromResponse(resp *http.Response) (*errorObject, error) // a specific slice. The responseHandler is responsible for closing the response. type responseHandler func(response *http.Response) (APIListObject, error) -func (c *Client) pagedGet(basePath string, handler responseHandler) error { +func (c *Client) pagedGet(ctx context.Context, basePath string, handler responseHandler) error { // Indicates whether there are still additional pages associated with request. var stillMore bool // Offset to set for the next page request. var nextOffset uint + basePrefix := getBasePrefix(basePath) // While there are more pages, keep adjusting the offset to get all results. for stillMore, nextOffset = true, 0; stillMore; { - response, err := c.do("GET", fmt.Sprintf("%s?offset=%d", basePath, nextOffset), nil, nil) + response, err := c.do(ctx, http.MethodGet, fmt.Sprintf("%soffset=%d", basePrefix, nextOffset), nil, nil) if err != nil { return err } @@ -215,3 +682,53 @@ func (c *Client) pagedGet(basePath string, handler responseHandler) error { return nil } + +type cursor struct { + // The minimum of the 'limit' parameter used in the request or + // the maximum request size of the API. + Limit uint + // An opaque string used to fetch the next set of results or 'null' + // if no additional results are available. + NextCursor string +} + +// cursorHandler is capable of parsing a response, using cursor-based pagination. +// At a minimum it must extract the page information for the current page. +type cursorHandler func(r *http.Response) (cursor, error) + +func (c *Client) cursorGet(ctx context.Context, basePath string, handler cursorHandler) error { + var next string + + basePrefix := getBasePrefix(basePath) + + for { + var cs string + if len(next) > 0 { + cs = fmt.Sprintf("cursor=%s", next) + } + + // The next set of results can be obtained by providing the + // NextCursor value from the previous request in a cursor + // query parameter on the subsequent request. + resp, err := c.do(ctx, http.MethodGet, fmt.Sprintf("%s%s", basePrefix, cs), nil, nil) + if err != nil { + return err + } + + // Call handler to extract page information and execute additional necessary handling. + c, err := handler(resp) + if err != nil { + return err + } + + // Stop parsing if there are no more results. + if len(c.NextCursor) == 0 { + break + } + + // Otherwise, update next to parse more results. + next = c.NextCursor + } + + return nil +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/constants.go b/vendor/github.com/PagerDuty/go-pagerduty/constants.go deleted file mode 100644 index b58d8cd..0000000 --- a/vendor/github.com/PagerDuty/go-pagerduty/constants.go +++ /dev/null @@ -1,5 +0,0 @@ -package pagerduty - -const ( - Version = "1.1.1" -) diff --git a/vendor/github.com/PagerDuty/go-pagerduty/escalation_policy.go b/vendor/github.com/PagerDuty/go-pagerduty/escalation_policy.go index 74befc6..085df8a 100644 --- a/vendor/github.com/PagerDuty/go-pagerduty/escalation_policy.go +++ b/vendor/github.com/PagerDuty/go-pagerduty/escalation_policy.go @@ -1,6 +1,7 @@ package pagerduty import ( + "context" "fmt" "net/http" @@ -21,13 +22,13 @@ type EscalationRule struct { // EscalationPolicy is a collection of escalation rules. type EscalationPolicy struct { APIObject - Name string `json:"name,omitempty"` - EscalationRules []EscalationRule `json:"escalation_rules,omitempty"` - Services []APIObject `json:"services,omitempty"` - NumLoops uint `json:"num_loops,omitempty"` - Teams []APIReference `json:"teams"` - Description string `json:"description,omitempty"` - RepeatEnabled bool `json:"repeat_enabled,omitempty"` + Name string `json:"name,omitempty"` + EscalationRules []EscalationRule `json:"escalation_rules,omitempty"` + Services []APIObject `json:"services,omitempty"` + NumLoops uint `json:"num_loops,omitempty"` + Teams []APIReference `json:"teams"` + Description string `json:"description,omitempty"` + OnCallHandoffNotifications string `json:"on_call_handoff_notifications,omitempty"` } // ListEscalationPoliciesResponse is the data structure returned from calling the ListEscalationPolicies API endpoint. @@ -36,6 +37,8 @@ type ListEscalationPoliciesResponse struct { EscalationPolicies []EscalationPolicy `json:"escalation_policies"` } +// ListEscalationRulesResponse represents the data structure returned when +// calling the ListEscalationRules API endpoint. type ListEscalationRulesResponse struct { APIListObject EscalationRules []EscalationRule `json:"escalation_rules"` @@ -43,7 +46,25 @@ type ListEscalationRulesResponse struct { // ListEscalationPoliciesOptions is the data structure used when calling the ListEscalationPolicies API endpoint. type ListEscalationPoliciesOptions struct { - APIListObject + // Limit is the pagination parameter that limits the number of results per + // page. PagerDuty defaults this value to 25 if omitted, and sets an upper + // bound of 100. + Limit uint `url:"limit,omitempty"` + + // Offset is the pagination parameter that specifies the offset at which to + // start pagination results. When trying to request the next page of + // results, the new Offset value should be currentOffset + Limit. + Offset uint `url:"offset,omitempty"` + + // Total is the pagination parameter to request that the API return the + // total count of items in the response. If this field is omitted or set to + // false, the total number of results will not be sent back from the PagerDuty API. + // + // Setting this to true will slow down the API response times, and so it's + // recommended to omit it unless you've a specific reason for wanting the + // total count of items in the collection. + Total bool `url:"total,omitempty"` + Query string `url:"query,omitempty"` UserIDs []string `url:"user_ids,omitempty,brackets"` TeamIDs []string `url:"team_ids,omitempty,brackets"` @@ -57,30 +78,59 @@ type GetEscalationRuleOptions struct { } // ListEscalationPolicies lists all of the existing escalation policies. +// +// Deprecated: Use ListEscalationPoliciesWithContext instead. func (c *Client) ListEscalationPolicies(o ListEscalationPoliciesOptions) (*ListEscalationPoliciesResponse, error) { + return c.ListEscalationPoliciesWithContext(context.Background(), o) +} + +// ListEscalationPoliciesWithContext lists all of the existing escalation policies. +func (c *Client) ListEscalationPoliciesWithContext(ctx context.Context, o ListEscalationPoliciesOptions) (*ListEscalationPoliciesResponse, error) { v, err := query.Values(o) if err != nil { return nil, err } - resp, err := c.get(escPath + "?" + v.Encode()) + + resp, err := c.get(ctx, escPath+"?"+v.Encode(), nil) if err != nil { return nil, err } + var result ListEscalationPoliciesResponse - return &result, c.decodeJSON(resp, &result) + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil } // CreateEscalationPolicy creates a new escalation policy. +// +// Deprecated: Use CreateEscalationPolicyWithContext instead. func (c *Client) CreateEscalationPolicy(e EscalationPolicy) (*EscalationPolicy, error) { - data := make(map[string]EscalationPolicy) - data["escalation_policy"] = e - resp, err := c.post(escPath, data, nil) + return c.CreateEscalationPolicyWithContext(context.Background(), e) +} + +// CreateEscalationPolicyWithContext creates a new escalation policy. +func (c *Client) CreateEscalationPolicyWithContext(ctx context.Context, e EscalationPolicy) (*EscalationPolicy, error) { + d := map[string]EscalationPolicy{ + "escalation_policy": e, + } + + resp, err := c.post(ctx, escPath, d, nil) return getEscalationPolicyFromResponse(c, resp, err) } // DeleteEscalationPolicy deletes an existing escalation policy and rules. +// +// Deprecated: Use DeleteEscalationPolicyWithContext instead. func (c *Client) DeleteEscalationPolicy(id string) error { - _, err := c.delete(escPath + "/" + id) + return c.DeleteEscalationPolicyWithContext(context.Background(), id) +} + +// DeleteEscalationPolicyWithContext deletes an existing escalation policy and rules. +func (c *Client) DeleteEscalationPolicyWithContext(ctx context.Context, id string) error { + _, err := c.delete(ctx, escPath+"/"+id) return err } @@ -89,98 +139,169 @@ type GetEscalationPolicyOptions struct { Includes []string `url:"include,omitempty,brackets"` } -// GetEscalationPolicy gets information about an existing escalation policy and its rules. +// GetEscalationPolicy gets information about an existing escalation policy and +// its rules. +// +// Deprecated: Use GetEscalationPolicyWithContext instead. func (c *Client) GetEscalationPolicy(id string, o *GetEscalationPolicyOptions) (*EscalationPolicy, error) { + return c.GetEscalationPolicyWithContext(context.Background(), id, o) +} + +// GetEscalationPolicyWithContext gets information about an existing escalation +// policy and its rules. +func (c *Client) GetEscalationPolicyWithContext(ctx context.Context, id string, o *GetEscalationPolicyOptions) (*EscalationPolicy, error) { v, err := query.Values(o) if err != nil { return nil, err } - resp, err := c.get(escPath + "/" + id + "?" + v.Encode()) + + resp, err := c.get(ctx, escPath+"/"+id+"?"+v.Encode(), nil) return getEscalationPolicyFromResponse(c, resp, err) } // UpdateEscalationPolicy updates an existing escalation policy and its rules. +// +// Deprecated: Use UpdateEscalationPolicyWithContext instead. func (c *Client) UpdateEscalationPolicy(id string, e *EscalationPolicy) (*EscalationPolicy, error) { - data := make(map[string]EscalationPolicy) - data["escalation_policy"] = *e - resp, err := c.put(escPath+"/"+id, data, nil) + return c.UpdateEscalationPolicyWithContext(context.Background(), id, *e) +} + +// UpdateEscalationPolicyWithContext updates an existing escalation policy and its rules. +func (c *Client) UpdateEscalationPolicyWithContext(ctx context.Context, id string, e EscalationPolicy) (*EscalationPolicy, error) { + d := map[string]EscalationPolicy{ + "escalation_policy": e, + } + + resp, err := c.put(ctx, escPath+"/"+id, d, nil) return getEscalationPolicyFromResponse(c, resp, err) } // CreateEscalationRule creates a new escalation rule for an escalation policy // and appends it to the end of the existing escalation rules. +// +// Deprecated: Use CreateEscalationRuleWithContext instead. func (c *Client) CreateEscalationRule(escID string, e EscalationRule) (*EscalationRule, error) { - data := make(map[string]EscalationRule) - data["escalation_rule"] = e - resp, err := c.post(escPath+"/"+escID+"/escalation_rules", data, nil) + return c.CreateEscalationRuleWithContext(context.Background(), escID, e) +} + +// CreateEscalationRuleWithContext creates a new escalation rule for an escalation policy +// and appends it to the end of the existing escalation rules. +func (c *Client) CreateEscalationRuleWithContext(ctx context.Context, escID string, e EscalationRule) (*EscalationRule, error) { + d := map[string]EscalationRule{ + "escalation_rule": e, + } + + resp, err := c.post(ctx, escPath+"/"+escID+"/escalation_rules", d, nil) return getEscalationRuleFromResponse(c, resp, err) } // GetEscalationRule gets information about an existing escalation rule. +// +// Deprecated: Use GetEscalationRuleWithContext instead. func (c *Client) GetEscalationRule(escID string, id string, o *GetEscalationRuleOptions) (*EscalationRule, error) { + return c.GetEscalationRuleWithContext(context.Background(), escID, id, o) +} + +// GetEscalationRuleWithContext gets information about an existing escalation rule. +func (c *Client) GetEscalationRuleWithContext(ctx context.Context, escID string, id string, o *GetEscalationRuleOptions) (*EscalationRule, error) { v, err := query.Values(o) if err != nil { return nil, err } - resp, err := c.get(escPath + "/" + escID + "/escalation_rules/" + id + "?" + v.Encode()) + + resp, err := c.get(ctx, escPath+"/"+escID+"/escalation_rules/"+id+"?"+v.Encode(), nil) return getEscalationRuleFromResponse(c, resp, err) } // DeleteEscalationRule deletes an existing escalation rule. +// +// Deprecated: Use DeleteEscalationRuleWithContext instead. func (c *Client) DeleteEscalationRule(escID string, id string) error { - _, err := c.delete(escPath + "/" + escID + "/escalation_rules/" + id) + return c.DeleteEscalationRuleWithContext(context.Background(), escID, id) +} + +// DeleteEscalationRuleWithContext deletes an existing escalation rule. +func (c *Client) DeleteEscalationRuleWithContext(ctx context.Context, escID string, id string) error { + _, err := c.delete(ctx, escPath+"/"+escID+"/escalation_rules/"+id) return err } // UpdateEscalationRule updates an existing escalation rule. +// +// Deprecated: Use UpdateEscalationRuleWithContext instead. func (c *Client) UpdateEscalationRule(escID string, id string, e *EscalationRule) (*EscalationRule, error) { - data := make(map[string]EscalationRule) - data["escalation_rule"] = *e - resp, err := c.put(escPath+"/"+escID+"/escalation_rules/"+id, data, nil) + return c.UpdateEscalationRuleWithContext(context.Background(), escID, id, *e) +} + +// UpdateEscalationRuleWithContext updates an existing escalation rule. +func (c *Client) UpdateEscalationRuleWithContext(ctx context.Context, escID string, id string, e EscalationRule) (*EscalationRule, error) { + d := map[string]EscalationRule{ + "escalation_rule": e, + } + + resp, err := c.put(ctx, escPath+"/"+escID+"/escalation_rules/"+id, d, nil) return getEscalationRuleFromResponse(c, resp, err) } -// ListEscalationRules lists all of the escalation rules for an existing escalation policy. +// ListEscalationRules lists all of the escalation rules for an existing +// escalation policy. +// +// Deprecated: Use ListEscalationRulesWithContext instead. func (c *Client) ListEscalationRules(escID string) (*ListEscalationRulesResponse, error) { - resp, err := c.get(escPath + "/" + escID + "/escalation_rules") + return c.ListEscalationRulesWithContext(context.Background(), escID) +} + +// ListEscalationRulesWithContext lists all of the escalation rules for an existing escalation policy. +func (c *Client) ListEscalationRulesWithContext(ctx context.Context, escID string) (*ListEscalationRulesResponse, error) { + resp, err := c.get(ctx, escPath+"/"+escID+"/escalation_rules", nil) if err != nil { return nil, err } var result ListEscalationRulesResponse - return &result, c.decodeJSON(resp, &result) + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil } func getEscalationRuleFromResponse(c *Client, resp *http.Response, err error) (*EscalationRule, error) { - defer resp.Body.Close() if err != nil { return nil, err } + var target map[string]EscalationRule if dErr := c.decodeJSON(resp, &target); dErr != nil { return nil, fmt.Errorf("Could not decode JSON response: %v", dErr) } - rootNode := "escalation_rule" + + const rootNode = "escalation_rule" + t, nodeOK := target[rootNode] if !nodeOK { return nil, fmt.Errorf("JSON response does not have %s field", rootNode) } + return &t, nil } func getEscalationPolicyFromResponse(c *Client, resp *http.Response, err error) (*EscalationPolicy, error) { - defer resp.Body.Close() if err != nil { return nil, err } + var target map[string]EscalationPolicy if dErr := c.decodeJSON(resp, &target); dErr != nil { return nil, fmt.Errorf("Could not decode JSON response: %v", dErr) } - rootNode := "escalation_policy" + + const rootNode = "escalation_policy" + t, nodeOK := target[rootNode] if !nodeOK { return nil, fmt.Errorf("JSON response does not have %s field", rootNode) } + return &t, nil } diff --git a/vendor/github.com/PagerDuty/go-pagerduty/event.go b/vendor/github.com/PagerDuty/go-pagerduty/event.go index e162fd5..d2241f0 100644 --- a/vendor/github.com/PagerDuty/go-pagerduty/event.go +++ b/vendor/github.com/PagerDuty/go-pagerduty/event.go @@ -26,7 +26,7 @@ type EventResponse struct { Status string `json:"status"` Message string `json:"message"` IncidentKey string `json:"incident_key"` - HttpStatus int + HTTPStatus int } // CreateEvent sends PagerDuty an event to trigger, acknowledge, or resolve a @@ -49,11 +49,13 @@ func CreateEventWithHTTPClient(e Event, client HTTPClient) (*EventResponse, erro req.Header.Set("Content-Type", "application/json") resp, err := client.Do(req) if err != nil { - return &EventResponse{HttpStatus: resp.StatusCode}, err + return nil, fmt.Errorf("failed to action request: %w", err) } - defer resp.Body.Close() + + defer func() { _ = resp.Body.Close() }() // explicitly discard error + if resp.StatusCode != http.StatusOK { - return &EventResponse{HttpStatus: resp.StatusCode}, fmt.Errorf("HTTP Status Code: %d", resp.StatusCode) + return &EventResponse{HTTPStatus: resp.StatusCode}, fmt.Errorf("HTTP Status Code: %d", resp.StatusCode) } var eventResponse EventResponse if err := json.NewDecoder(resp.Body).Decode(&eventResponse); err != nil { diff --git a/vendor/github.com/PagerDuty/go-pagerduty/event_orchestration.go b/vendor/github.com/PagerDuty/go-pagerduty/event_orchestration.go new file mode 100644 index 0000000..7abc60d --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/event_orchestration.go @@ -0,0 +1,486 @@ +package pagerduty + +import ( + "context" + "fmt" + "net/http" + + "github.com/google/go-querystring/query" +) + +const ( + eoPath = "/event_orchestrations" +) + +// Orchestration defines a global orchestration to route events the same source +// to different services. +type Orchestration struct { + APIObject + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Team *APIReference `json:"team,omitempty"` + Integrations []*OrchestrationIntegration `json:"integrations,omitempty"` + Routes uint `json:"routes,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + CreatedBy *APIReference `json:"created_by,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` + UpdatedBy *APIReference `json:"updated_by,omitempty"` + Version string `json:"version,omitempty"` +} + +// OrchestrationIntegration is a route into an orchestration. +type OrchestrationIntegration struct { + ID string `json:"id,omitempty"` + Parameters *OrchestrationIntegrationParameters `json:"parameters,omitempty"` +} + +type OrchestrationIntegrationParameters struct { + RoutingKey string `json:"routing_key,omitempty"` + Type string `json:"type,omitempty"` +} + +// ListOrchestrationsResponse is the data structure returned from calling the ListOrchestrations API endpoint. +type ListOrchestrationsResponse struct { + APIListObject + Orchestrations []Orchestration `json:"orchestrations"` +} + +// ListOrchestrationsOptions is the data structure used when calling the ListOrchestrations API endpoint. +type ListOrchestrationsOptions struct { + // Limit is the pagination parameter that limits the number of results per + // page. PagerDuty defaults this value to 25 if omitted, and sets an upper + // bound of 100. + Limit uint `url:"limit,omitempty"` + + // Offset is the pagination parameter that specifies the offset at which to + // start pagination results. When trying to request the next page of + // results, the new Offset value should be currentOffset + Limit. + Offset uint `url:"offset,omitempty"` + + // Total is the pagination parameter to request that the API return the + // total count of items in the response. If this field is omitted or set to + // false, the total number of results will not be sent back from the PagerDuty API. + // + // Setting this to true will slow down the API response times, and so it's + // recommended to omit it unless you've a specific reason for wanting the + // total count of items in the collection. + Total bool `url:"total,omitempty"` + + SortBy string `url:"sort_by,omitempty"` +} + +// ListOrchestrationsWithContext lists all the existing event orchestrations. +func (c *Client) ListOrchestrationsWithContext(ctx context.Context, o ListOrchestrationsOptions) (*ListOrchestrationsResponse, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + + resp, err := c.get(ctx, eoPath+"?"+v.Encode(), nil) + if err != nil { + return nil, err + } + + var result ListOrchestrationsResponse + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil +} + +// CreateOrchestrationWithContext creates a new event orchestration. +func (c *Client) CreateOrchestrationWithContext(ctx context.Context, e Orchestration) (*Orchestration, error) { + d := map[string]Orchestration{ + "orchestration": e, + } + + resp, err := c.post(ctx, eoPath, d, nil) + return getOrchestrationFromResponse(c, resp, err) +} + +// DeleteOrchestrationWithContext deletes an existing event orchestration. +func (c *Client) DeleteOrchestrationWithContext(ctx context.Context, id string) error { + _, err := c.delete(ctx, eoPath+"/"+id) + return err +} + +// GetOrchestrationOptions is the data structure used when calling the GetOrchestration API endpoint. +type GetOrchestrationOptions struct { +} + +// GetOrchestrationWithContext gets information about an event orchestration. +func (c *Client) GetOrchestrationWithContext(ctx context.Context, id string, o *GetOrchestrationOptions) (*Orchestration, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + + resp, err := c.get(ctx, eoPath+"/"+id+"?"+v.Encode(), nil) + return getOrchestrationFromResponse(c, resp, err) +} + +// UpdateOrchestrationWithContext updates an existing event orchestration. +func (c *Client) UpdateOrchestrationWithContext(ctx context.Context, id string, e Orchestration) (*Orchestration, error) { + d := map[string]Orchestration{ + "orchestration": e, + } + + resp, err := c.put(ctx, eoPath+"/"+id, d, nil) + return getOrchestrationFromResponse(c, resp, err) +} + +func getOrchestrationFromResponse(c *Client, resp *http.Response, err error) (*Orchestration, error) { + if err != nil { + return nil, err + } + + var target map[string]Orchestration + if dErr := c.decodeJSON(resp, &target); dErr != nil { + return nil, fmt.Errorf("could not decode JSON response: %v", dErr) + } + + const rootNode = "orchestration" + + t, nodeOK := target[rootNode] + if !nodeOK { + return nil, fmt.Errorf("JSON response does not have %s field", rootNode) + } + + return &t, nil +} + +// OrchestrationRouter is an event router. +type OrchestrationRouter struct { + Type string `json:"type,omitempty"` + Parent *APIReference `json:"parent,omitempty"` + Sets []*OrchestrationRouterRuleSet `json:"sets,omitempty"` + CatchAll *OrchestrationRouterCatchAllRule `json:"catch_all,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + CreatedBy *APIReference `json:"created_by,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` + UpdatedBy *APIReference `json:"updated_by,omitempty"` + Version string `json:"version,omitempty"` +} + +type OrchestrationRouterRuleSet struct { + ID string `json:"id,omitempty"` + Rules []*OrchestrationRouterRule `json:"rules,omitempty"` +} + +type OrchestrationRouterRule struct { + ID string `json:"id,omitempty"` + Label string `json:"label,omitempty"` + Conditions []*OrchestrationRouterRuleCondition `json:"conditions,omitempty"` + Actions *OrchestrationRouterActions `json:"actions,omitempty"` + Disabled bool `json:"disabled,omitempty"` +} + +type OrchestrationRouterRuleCondition struct { + Expression string `json:"expression,omitempty"` +} + +// OrchestrationRouterCatchAllRule routes an event when none of the rules match an event. +type OrchestrationRouterCatchAllRule struct { + Actions *OrchestrationRouterActions `json:"actions,omitempty"` +} + +// OrchestrationRouterActions are the actions that will be taken to change the resulting alert and incident. +type OrchestrationRouterActions struct { + RouteTo string `json:"route_to,omitempty"` +} + +// GetOrchestrationRouterOptions is the data structure used when calling the GetOrchestrationRouter API endpoint. +type GetOrchestrationRouterOptions struct { +} + +// GetOrchestrationRouterWithContext gets information about an event orchestration. +func (c *Client) GetOrchestrationRouterWithContext(ctx context.Context, id string, o *GetOrchestrationRouterOptions) (*OrchestrationRouter, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + + resp, err := c.get(ctx, eoPath+"/"+id+"/router"+"?"+v.Encode(), nil) + return getOrchestrationRouterFromResponse(c, resp, err) +} + +// UpdateOrchestrationRouterWithContext updates the routing rules of an existing event orchestration. +func (c *Client) UpdateOrchestrationRouterWithContext(ctx context.Context, id string, e OrchestrationRouter) (*OrchestrationRouter, error) { + d := map[string]OrchestrationRouter{ + "orchestration_path": e, + } + + resp, err := c.put(ctx, eoPath+"/"+id+"/router", d, nil) + return getOrchestrationRouterFromResponse(c, resp, err) +} + +func getOrchestrationRouterFromResponse(c *Client, resp *http.Response, err error) (*OrchestrationRouter, error) { + if err != nil { + return nil, err + } + + var target map[string]OrchestrationRouter + if dErr := c.decodeJSON(resp, &target); dErr != nil { + return nil, fmt.Errorf("could not decode JSON response: %v", dErr) + } + + const rootNode = "orchestration_path" + + t, nodeOK := target[rootNode] + if !nodeOK { + return nil, fmt.Errorf("JSON response does not have %s field", rootNode) + } + + return &t, nil +} + +// ServiceOrchestration defines sets of rules belonging to a service. +type ServiceOrchestration struct { + Type string `json:"type,omitempty"` + Parent *APIReference `json:"parent,omitempty"` + Sets []*ServiceOrchestrationRuleSet `json:"sets,omitempty"` + CatchAll *ServiceOrchestrationCatchAllRule `json:"catch_all,omitempty"` + + CreatedAt string `json:"created_at,omitempty"` + CreatedBy *APIReference `json:"created_by,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` + UpdatedBy *APIReference `json:"updated_by,omitempty"` + Version string `json:"version,omitempty"` +} + +type ServiceOrchestrationCatchAllRule struct { + Actions *ServiceOrchestrationRuleActions `json:"actions,omitempty"` +} + +type ServiceOrchestrationRuleSet struct { + ID string `json:"id,omitempty"` + Rules []*ServiceOrchestrationRule `json:"rules,omitempty"` +} + +type ServiceOrchestrationRule struct { + ID string `json:"id,omitempty"` + Label string `json:"label,omitempty"` + Conditions []*ServiceOrchestrationRuleCondition `json:"conditions,omitempty"` + Actions *ServiceOrchestrationRuleActions `json:"actions,omitempty"` + Disabled bool `json:"disabled,omitempty"` +} + +type ServiceOrchestrationRuleCondition struct { + Expression string `json:"expression,omitempty"` +} + +// ServiceOrchestrationRuleActions are the actions that will be taken to change the resulting alert and incident. +type ServiceOrchestrationRuleActions struct { + RouteTo string `json:"route_to,omitempty"` + Suppress bool `json:"suppress,omitempty"` + Suspend uint `json:"suspend,omitempty"` + Priority string `json:"priority,omitempty"` + Annotate string `json:"annotate,omitempty"` + PagerDutyAutomationActions []*PagerDutyAutomationAction `json:"pagerduty_automation_actions,omitempty"` + AutomationActions []*AutomationAction `json:"automation_actions,omitempty"` + Severity string `json:"severity,omitempty"` + EventAction string `json:"event_action,omitempty"` + Variables []*OrchestrationVariable `json:"variables,omitempty"` + Extractions []*OrchestrationExtraction `json:"extractions,omitempty"` +} + +type ServiceOrchestrationActive struct { + Active bool `json:"active,omitempty"` +} + +// GetServiceOrchestrationOptions is the data structure used when calling the GetServiceOrchestration API endpoint. +type GetServiceOrchestrationOptions struct { +} + +// GetServiceOrchestrationWithContext gets information about a service orchestration. +func (c *Client) GetServiceOrchestrationWithContext(ctx context.Context, id string, o *GetServiceOrchestrationOptions) (*ServiceOrchestration, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + + resp, err := c.get(ctx, eoPath+"/services/"+id+"?"+v.Encode(), nil) + return getServiceOrchestrationFromResponse(c, resp, err) +} + +// UpdateServiceOrchestrationWithContext updates the routing rules of a service orchestration. +func (c *Client) UpdateServiceOrchestrationWithContext(ctx context.Context, id string, e ServiceOrchestration) (*ServiceOrchestration, error) { + d := map[string]ServiceOrchestration{ + "orchestration_path": e, + } + + resp, err := c.put(ctx, eoPath+"/services/"+id, d, nil) + return getServiceOrchestrationFromResponse(c, resp, err) +} + +// GetServiceOrchestrationActiveWithContext gets a service orchestration's active status. +func (c *Client) GetServiceOrchestrationActiveWithContext(ctx context.Context, id string) (*ServiceOrchestrationActive, error) { + resp, err := c.get(ctx, eoPath+"/services/"+id+"/active", nil) + return getServiceOrchestrationActiveFromResponse(c, resp, err) +} + +// UpdateServiceOrchestrationActiveWithContext updates a service orchestration's active status. +func (c *Client) UpdateServiceOrchestrationActiveWithContext(ctx context.Context, id string, e ServiceOrchestrationActive) (*ServiceOrchestrationActive, error) { + resp, err := c.put(ctx, eoPath+"/services/"+id+"/active", e, nil) + return getServiceOrchestrationActiveFromResponse(c, resp, err) +} + +func getServiceOrchestrationFromResponse(c *Client, resp *http.Response, err error) (*ServiceOrchestration, error) { + if err != nil { + return nil, err + } + + var target map[string]ServiceOrchestration + if dErr := c.decodeJSON(resp, &target); dErr != nil { + return nil, fmt.Errorf("could not decode JSON response: %v", dErr) + } + + const rootNode = "orchestration_path" + + t, nodeOK := target[rootNode] + if !nodeOK { + return nil, fmt.Errorf("JSON response does not have %s field", rootNode) + } + + return &t, nil +} + +func getServiceOrchestrationActiveFromResponse(c *Client, resp *http.Response, err error) (*ServiceOrchestrationActive, error) { + if err != nil { + return nil, err + } + + var target ServiceOrchestrationActive + if dErr := c.decodeJSON(resp, &target); dErr != nil { + return nil, fmt.Errorf("could not decode JSON response: %v", dErr) + } + + return &target, nil +} + +// OrchestrationUnrouted defines sets of rules to be applied to unrouted events. +type OrchestrationUnrouted struct { + Type string `json:"type,omitempty"` + Parent *APIReference `json:"parent,omitempty"` + Sets []*ServiceOrchestrationRuleSet `json:"sets,omitempty"` + CatchAll *OrchestrationUnroutedCatchAllRule `json:"catch_all,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + CreatedBy *APIReference `json:"created_by,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` + UpdatedBy *APIReference `json:"updated_by,omitempty"` + Version string `json:"version,omitempty"` +} + +type OrchestrationUnroutedCatchAllRule struct { + Actions *OrchestrationUnroutedRuleActions `json:"actions,omitempty"` +} + +type OrchestrationUnroutedRuleSet struct { + ID string `json:"id,omitempty"` + Rules []*OrchestrationUnroutedRule `json:"rules,omitempty"` +} + +type OrchestrationUnroutedRule struct { + ID string `json:"id,omitempty"` + Label string `json:"label,omitempty"` + Conditions []*OrchestrationUnroutedRuleCondition `json:"conditions,omitempty"` + Actions *OrchestrationUnroutedRuleActions `json:"actions,omitempty"` + Disabled bool `json:"disabled,omitempty"` +} + +type OrchestrationUnroutedRuleCondition struct { + Expression string `json:"expression,omitempty"` +} + +// OrchestrationUnroutedRuleActions are the actions that will be taken to change the resulting alert and incident. +type OrchestrationUnroutedRuleActions struct { + RouteTo string `json:"route_to,omitempty"` + Severity string `json:"severity,omitempty"` + EventAction string `json:"event_action,omitempty"` + Variables []*OrchestrationVariable `json:"variables,omitempty"` + Extractions []*OrchestrationExtraction `json:"extractions,omitempty"` +} + +// GetOrchestrationUnroutedOptions is the data structure used when calling the GetOrchestrationUnrouted API endpoint. +type GetOrchestrationUnroutedOptions struct { +} + +// GetOrchestrationUnroutedWithContext gets the routing rules for unrouted events. +func (c *Client) GetOrchestrationUnroutedWithContext(ctx context.Context, id string, o *GetOrchestrationUnroutedOptions) (*OrchestrationUnrouted, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + + resp, err := c.get(ctx, eoPath+"/"+id+"/unrouted"+"?"+v.Encode(), nil) + return getOrchestrationUnroutedFromResponse(c, resp, err) +} + +// UpdateOrchestrationUnroutedWithContext updates the routing rules for unrouted events. +func (c *Client) UpdateOrchestrationUnroutedWithContext(ctx context.Context, id string, e OrchestrationUnrouted) (*OrchestrationUnrouted, error) { + d := map[string]OrchestrationUnrouted{ + "orchestration_path": e, + } + + resp, err := c.put(ctx, eoPath+"/"+id+"/unrouted", d, nil) + return getOrchestrationUnroutedFromResponse(c, resp, err) +} + +func getOrchestrationUnroutedFromResponse(c *Client, resp *http.Response, err error) (*OrchestrationUnrouted, error) { + if err != nil { + return nil, err + } + + var target map[string]OrchestrationUnrouted + if dErr := c.decodeJSON(resp, &target); dErr != nil { + return nil, fmt.Errorf("could not decode JSON response: %v", dErr) + } + + const rootNode = "orchestration_path" + + t, nodeOK := target[rootNode] + if !nodeOK { + return nil, fmt.Errorf("JSON response does not have %s field", rootNode) + } + + return &t, nil +} + +type PagerDutyAutomationAction struct { + ActionID string `json:"action_id,omitempty"` +} + +type AutomationAction struct { + Name string `json:"name,omitempty"` + URL string `json:"url,omitempty"` + AutoSend bool `json:"auto_send,omitempty"` + Headers []*OrchestrationHeader `json:"headers,omitempty"` + Parameters []*OrchestrationParameter `json:"parameters,omitempty"` +} + +type OrchestrationHeader struct { + Key string `json:"key,omitempty"` + Value string `json:"value,omitempty"` +} + +type OrchestrationParameter struct { + Key string `json:"key,omitempty"` + Value string `json:"value,omitempty"` +} + +// OrchestrationExtraction defines a value extraction in an orchestration rule. +type OrchestrationExtraction struct { + Target string `json:"target,omitempty"` + Regex string `json:"regex,omitempty"` + Source string `json:"source,omitempty"` + Template string `json:"template,omitempty"` +} + +// OrchestrationVariable defines a variable in an orchestration rule. +type OrchestrationVariable struct { + Name string `json:"name,omitempty"` + Path string `json:"path,omitempty"` + Type string `json:"type,omitempty"` + Value string `json:"value,omitempty"` +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/event_v2.go b/vendor/github.com/PagerDuty/go-pagerduty/event_v2.go index 4e5bc4a..6e7dda3 100644 --- a/vendor/github.com/PagerDuty/go-pagerduty/event_v2.go +++ b/vendor/github.com/PagerDuty/go-pagerduty/event_v2.go @@ -2,13 +2,14 @@ package pagerduty import ( "bytes" + "context" "encoding/json" "fmt" "io/ioutil" "net/http" ) -// Event includes the incident/alert details +// V2Event includes the incident/alert details type V2Event struct { RoutingKey string `json:"routing_key"` Action string `json:"event_action"` @@ -20,7 +21,7 @@ type V2Event struct { Payload *V2Payload `json:"payload,omitempty"` } -// Payload represents the individual event details for an event +// V2Payload represents the individual event details for an event type V2Payload struct { Summary string `json:"summary"` Source string `json:"source"` @@ -32,7 +33,7 @@ type V2Payload struct { Details interface{} `json:"custom_details,omitempty"` } -// Response is the json response body for an event +// V2EventResponse is the json response body for an event type V2EventResponse struct { Status string `json:"status,omitempty"` DedupKey string `json:"dedup_key,omitempty"` @@ -42,30 +43,209 @@ type V2EventResponse struct { const v2eventEndPoint = "https://events.pagerduty.com/v2/enqueue" -// ManageEvent handles the trigger, acknowledge, and resolve methods for an event +// ManageEvent handles the trigger, acknowledge, and resolve methods for an +// event. +// +// Deprecated: Use ManageEventWithContext instead. func ManageEvent(e V2Event) (*V2EventResponse, error) { + return ManageEventWithContext(context.Background(), e) +} + +// EventsAPIV2Error represents the error response received when an Events API V2 call fails. The +// HTTP response code is set inside of the StatusCode field, with the EventsAPIV2Error +// field being the wrapper around the JSON error object returned from the Events API V2. +// +// This type also provides some helper methods like .BadRequest(), .RateLimited(), +// and .Temporary() to help callers reason about how to handle the error. +type EventsAPIV2Error struct { + // StatusCode is the HTTP response status code. + StatusCode int `json:"-"` + + // APIError represents the object returned by the API when an error occurs, + // which includes messages that should hopefully provide useful context + // to the end user. + // + // If the API response did not contain an error object, the .Valid field of + // APIError will be false. If .Valid is true, the .ErrorObject field is + // valid and should be consulted. + APIError NullEventsAPIV2ErrorObject + + message string +} + +var _ json.Unmarshaler = (*EventsAPIV2Error)(nil) // assert that it satisfies the json.Unmarshaler interface. + +// Error satisfies the error interface, and should contain the StatusCode, +// APIError.Message, APIError.ErrorObject.Status, and APIError.Errors. +func (e EventsAPIV2Error) Error() string { + if len(e.message) > 0 { + return e.message + } + + if !e.APIError.Valid { + return fmt.Sprintf("HTTP response failed with status code %d and no JSON error object was present", e.StatusCode) + } + + if len(e.APIError.ErrorObject.Errors) == 0 { + return fmt.Sprintf( + "HTTP response failed with status code %d, status: %s, message: %s", + e.StatusCode, e.APIError.ErrorObject.Status, e.APIError.ErrorObject.Message, + ) + } + + return fmt.Sprintf( + "HTTP response failed with status code %d, status: %s, message: %s: %s", + e.StatusCode, + e.APIError.ErrorObject.Status, + e.APIError.ErrorObject.Message, + apiErrorsDetailString(e.APIError.ErrorObject.Errors), + ) +} + +// UnmarshalJSON satisfies encoding/json.Unmarshaler. +func (e *EventsAPIV2Error) UnmarshalJSON(data []byte) error { + var eo EventsAPIV2ErrorObject + if err := json.Unmarshal(data, &eo); err != nil { + return err + } + + e.APIError.ErrorObject = eo + e.APIError.Valid = true + + return nil +} + +// BadRequest returns whether the event request was rejected by PagerDuty as an +// incorrect or invalid event structure. +func (e EventsAPIV2Error) BadRequest() bool { + return e.StatusCode == http.StatusBadRequest +} + +// RateLimited returns whether the response had a status of 429, and as such the +// client is rate limited. The PagerDuty rate limits should reset once per +// minute, and for the REST API they are an account-wide rate limit (not per +// API key or IP). +func (e EventsAPIV2Error) RateLimited() bool { + return e.StatusCode == http.StatusTooManyRequests +} + +// APITimeout returns whether whether the response had a status of 408, +// indicating there was a request timeout on PagerDuty's side. This error is +// considered temporary, and so the request should be retried. +// +// Please note, this does not returnn true if the Go context.Context deadline +// was exceeded when making the request. +func (e EventsAPIV2Error) APITimeout() bool { + return e.StatusCode == http.StatusRequestTimeout +} + +// Temporary returns whether it was a temporary error, one of which is a +// RateLimited error. +func (e EventsAPIV2Error) Temporary() bool { + return e.RateLimited() || e.APITimeout() || (e.StatusCode >= 500 && e.StatusCode < 600) +} + +// NullEventsAPIV2ErrorObject is a wrapper around the EventsAPIV2ErrorObject type. If the Valid +// field is true, the API response included a structured error JSON object. This +// structured object is then set on the ErrorObject field. +// +// We assume it's possible in exceptional failure modes for error objects to be omitted by PagerDuty. +// As such, this wrapper type provides us a way to check if the object was +// provided while avoiding consumers accidentally missing a nil pointer check, +// thus crashing their whole program. +type NullEventsAPIV2ErrorObject struct { + Valid bool + ErrorObject EventsAPIV2ErrorObject +} + +// EventsAPIV2ErrorObject represents the object returned by the Events V2 API when an error +// occurs. This includes messages that should hopefully provide useful context +// to the end user. +type EventsAPIV2ErrorObject struct { + Status string `json:"status,omitempty"` + Message string `json:"message,omitempty"` + + // Errors is likely to be empty, with the relevant error presented via the + // Status field instead. + Errors []string `json:"errors,omitempty"` +} + +// ManageEventWithContext handles the trigger, acknowledge, and resolve methods for an event. +func ManageEventWithContext(ctx context.Context, e V2Event) (*V2EventResponse, error) { data, err := json.Marshal(e) if err != nil { return nil, err } - req, _ := http.NewRequest("POST", v2eventEndPoint, bytes.NewBuffer(data)) + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, v2eventEndPoint, bytes.NewBuffer(data)) + if err != nil { + return nil, fmt.Errorf("failed to create HTTP request: %w", err) + } + req.Header.Set("User-Agent", "go-pagerduty/"+Version) req.Header.Set("Content-Type", "application/json") + + // TODO(theckman): switch to a package-local default client resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } - defer resp.Body.Close() + + defer func() { _ = resp.Body.Close() }() // explicitly discard error if resp.StatusCode != http.StatusAccepted { - bytes, err := ioutil.ReadAll(resp.Body) + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, EventsAPIV2Error{ + StatusCode: resp.StatusCode, + message: fmt.Sprintf("HTTP response with status code: %d: error: %s", resp.StatusCode, err), + } + } + // now try to decode the response body into the error object. + var eae EventsAPIV2Error + err = json.Unmarshal(b, &eae) if err != nil { - return nil, fmt.Errorf("HTTP Status Code: %d", resp.StatusCode) + eae = EventsAPIV2Error{ + StatusCode: resp.StatusCode, + message: fmt.Sprintf("HTTP response with status code: %d, JSON unmarshal object body failed: %s, body: %s", resp.StatusCode, err, string(b)), + } + return nil, eae } - return nil, fmt.Errorf("HTTP Status Code: %d, Message: %s", resp.StatusCode, string(bytes)) + + eae.StatusCode = resp.StatusCode + return nil, eae } + var eventResponse V2EventResponse if err := json.NewDecoder(resp.Body).Decode(&eventResponse); err != nil { return nil, err } return &eventResponse, nil } + +// ManageEvent handles the trigger, acknowledge, and resolve methods for an +// event. +// +// Deprecated: Use ManageEventWithContext instead. +func (c *Client) ManageEvent(e *V2Event) (*V2EventResponse, error) { + return c.ManageEventWithContext(context.Background(), e) +} + +// ManageEventWithContext handles the trigger, acknowledge, and resolve methods for an event. +func (c *Client) ManageEventWithContext(ctx context.Context, e *V2Event) (*V2EventResponse, error) { + data, err := json.Marshal(e) + if err != nil { + return nil, err + } + + resp, err := c.doWithEndpoint(ctx, c.v2EventsAPIEndpoint, http.MethodPost, "/v2/enqueue", false, bytes.NewBuffer(data), nil) + if err != nil { + return nil, err + } + + var result V2EventResponse + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, err +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/extension.go b/vendor/github.com/PagerDuty/go-pagerduty/extension.go index 3e6722b..f1c112e 100644 --- a/vendor/github.com/PagerDuty/go-pagerduty/extension.go +++ b/vendor/github.com/PagerDuty/go-pagerduty/extension.go @@ -1,66 +1,140 @@ package pagerduty import ( + "context" "fmt" "net/http" "github.com/google/go-querystring/query" ) +// Extension represents a single PagerDuty extension. These are addtional +// features to be used as part of the incident management process. type Extension struct { APIObject - Name string `json:"name"` - EndpointURL string `json:"endpoint_url"` - ExtensionObjects []APIObject `json:"extension_objects"` - ExtensionSchema APIObject `json:"extension_schema"` - Config interface{} `json:"config"` + Name string `json:"name"` + EndpointURL string `json:"endpoint_url,omitempty"` + ExtensionObjects []APIObject `json:"extension_objects"` + ExtensionSchema APIObject `json:"extension_schema"` + Config interface{} `json:"config"` + TemporarilyDisabled bool `json:"temporarily_disabled,omitempty"` } +// ListExtensionResponse represents the single response from the PagerDuty API +// when listing extensions. type ListExtensionResponse struct { APIListObject Extensions []Extension `json:"extensions"` } +// ListExtensionOptions are the options to use when listing extensions. type ListExtensionOptions struct { - APIListObject + // Limit is the pagination parameter that limits the number of results per + // page. PagerDuty defaults this value to 25 if omitted, and sets an upper + // bound of 100. + Limit uint `url:"limit,omitempty"` + + // Offset is the pagination parameter that specifies the offset at which to + // start pagination results. When trying to request the next page of + // results, the new Offset value should be currentOffset + Limit. + Offset uint `url:"offset,omitempty"` + + // Total is the pagination parameter to request that the API return the + // total count of items in the response. If this field is omitted or set to + // false, the total number of results will not be sent back from the PagerDuty API. + // + // Setting this to true will slow down the API response times, and so it's + // recommended to omit it unless you've a specific reason for wanting the + // total count of items in the collection. + Total bool `url:"total,omitempty"` + ExtensionObjectID string `url:"extension_object_id,omitempty"` ExtensionSchemaID string `url:"extension_schema_id,omitempty"` Query string `url:"query,omitempty"` } +// ListExtensions lists the extensions from the API. +// +// Deprecated: Use ListExtensionsWithContext instead. func (c *Client) ListExtensions(o ListExtensionOptions) (*ListExtensionResponse, error) { + return c.ListExtensionsWithContext(context.Background(), o) +} + +// ListExtensionsWithContext lists the extensions from the API. +func (c *Client) ListExtensionsWithContext(ctx context.Context, o ListExtensionOptions) (*ListExtensionResponse, error) { v, err := query.Values(o) if err != nil { return nil, err } - resp, err := c.get("/extensions?" + v.Encode()) + resp, err := c.get(ctx, "/extensions?"+v.Encode(), nil) if err != nil { return nil, err } var result ListExtensionResponse + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } - return &result, c.decodeJSON(resp, &result) + return &result, nil } +// CreateExtension creates a single extension. +// +// Deprecated: Use CreateExtensionWithContext instead. func (c *Client) CreateExtension(e *Extension) (*Extension, error) { - resp, err := c.post("/extensions", e, nil) + return c.CreateExtensionWithContext(context.Background(), e) +} + +// CreateExtensionWithContext creates a single extension. +func (c *Client) CreateExtensionWithContext(ctx context.Context, e *Extension) (*Extension, error) { + resp, err := c.post(ctx, "/extensions", e, nil) return getExtensionFromResponse(c, resp, err) } +// DeleteExtension deletes an extension by its ID. +// +// Deprecated: Use DeleteExtensionWithContext instead. func (c *Client) DeleteExtension(id string) error { - _, err := c.delete("/extensions/" + id) + return c.DeleteExtensionWithContext(context.Background(), id) +} + +// DeleteExtensionWithContext deletes an extension by its ID. +func (c *Client) DeleteExtensionWithContext(ctx context.Context, id string) error { + _, err := c.delete(ctx, "/extensions/"+id) return err } +// GetExtension gets an extension by its ID. +// +// Deprecated: Use GetExtensionWithContext instead. func (c *Client) GetExtension(id string) (*Extension, error) { - resp, err := c.get("/extensions/" + id) + return c.GetExtensionWithContext(context.Background(), id) +} + +// GetExtensionWithContext gets an extension by its ID. +func (c *Client) GetExtensionWithContext(ctx context.Context, id string) (*Extension, error) { + resp, err := c.get(ctx, "/extensions/"+id, nil) return getExtensionFromResponse(c, resp, err) } +// UpdateExtension updates an extension by its ID. +// +// Deprecated: Use UpdateExtensionWithContext instead. func (c *Client) UpdateExtension(id string, e *Extension) (*Extension, error) { - resp, err := c.put("/extensions/"+id, e, nil) + return c.UpdateExtensionWithContext(context.Background(), id, e) +} + +// UpdateExtensionWithContext updates an extension by its ID. +func (c *Client) UpdateExtensionWithContext(ctx context.Context, id string, e *Extension) (*Extension, error) { + resp, err := c.put(ctx, "/extensions/"+id, e, nil) + return getExtensionFromResponse(c, resp, err) +} + +// EnableExtension enables a temporarily disabled extension by its ID. +func (c *Client) EnableExtension(ctx context.Context, id string) (*Extension, error) { + resp, err := c.post(ctx, "/extensions/"+id+"/enable", nil, nil) return getExtensionFromResponse(c, resp, err) } @@ -74,7 +148,7 @@ func getExtensionFromResponse(c *Client, resp *http.Response, err error) (*Exten return nil, fmt.Errorf("Could not decode JSON response: %v", dErr) } - rootNode := "extension" + const rootNode = "extension" t, nodeOK := target[rootNode] if !nodeOK { diff --git a/vendor/github.com/PagerDuty/go-pagerduty/extension_schema.go b/vendor/github.com/PagerDuty/go-pagerduty/extension_schema.go index eeee2dd..fe8d146 100644 --- a/vendor/github.com/PagerDuty/go-pagerduty/extension_schema.go +++ b/vendor/github.com/PagerDuty/go-pagerduty/extension_schema.go @@ -1,12 +1,15 @@ package pagerduty import ( + "context" "fmt" "net/http" "github.com/google/go-querystring/query" ) +// ExtensionSchema represnts the object presented by the API for each extension +// schema. type ExtensionSchema struct { APIObject IconURL string `json:"icon_url"` @@ -19,34 +22,75 @@ type ExtensionSchema struct { URL string `json:"url"` } +// ListExtensionSchemaResponse is the object presented in response to the +// request to list all extension schemas. type ListExtensionSchemaResponse struct { APIListObject ExtensionSchemas []ExtensionSchema `json:"extension_schemas"` } +// ListExtensionSchemaOptions are the options to send with the +// ListExtensionSchema reques(s). type ListExtensionSchemaOptions struct { - APIListObject - Query string `url:"query,omitempty"` + // Limit is the pagination parameter that limits the number of results per + // page. PagerDuty defaults this value to 25 if omitted, and sets an upper + // bound of 100. + Limit uint `url:"limit,omitempty"` + + // Offset is the pagination parameter that specifies the offset at which to + // start pagination results. When trying to request the next page of + // results, the new Offset value should be currentOffset + Limit. + Offset uint `url:"offset,omitempty"` + + // Total is the pagination parameter to request that the API return the + // total count of items in the response. If this field is omitted or set to + // false, the total number of results will not be sent back from the PagerDuty API. + // + // Setting this to true will slow down the API response times, and so it's + // recommended to omit it unless you've a specific reason for wanting the + // total count of items in the collection. + Total bool `url:"total,omitempty"` } +// ListExtensionSchemas lists all of the extension schemas. Each schema +// represents a specific type of outbound extension. +// +// Deprecated: Use ListExtensionSchemasWithContext instead. func (c *Client) ListExtensionSchemas(o ListExtensionSchemaOptions) (*ListExtensionSchemaResponse, error) { + return c.ListExtensionSchemasWithContext(context.Background(), o) +} + +// ListExtensionSchemasWithContext lists all of the extension schemas. Each +// schema represents a specific type of outbound extension. +func (c *Client) ListExtensionSchemasWithContext(ctx context.Context, o ListExtensionSchemaOptions) (*ListExtensionSchemaResponse, error) { v, err := query.Values(o) if err != nil { return nil, err } - resp, err := c.get("/extension_schemas?" + v.Encode()) + resp, err := c.get(ctx, "/extension_schemas?"+v.Encode(), nil) if err != nil { return nil, err } var result ListExtensionSchemaResponse + if err := c.decodeJSON(resp, &result); err != nil { + return nil, err + } - return &result, c.decodeJSON(resp, &result) + return &result, nil } +// GetExtensionSchema gets a single extension schema. +// +// Deprecated: Use GetExtensionSchemaWithContext instead. func (c *Client) GetExtensionSchema(id string) (*ExtensionSchema, error) { - resp, err := c.get("/extension_schemas/" + id) + return c.GetExtensionSchemaWithContext(context.Background(), id) +} + +// GetExtensionSchemaWithContext gets a single extension schema. +func (c *Client) GetExtensionSchemaWithContext(ctx context.Context, id string) (*ExtensionSchema, error) { + resp, err := c.get(ctx, "/extension_schemas/"+id, nil) return getExtensionSchemaFromResponse(c, resp, err) } @@ -60,7 +104,8 @@ func getExtensionSchemaFromResponse(c *Client, resp *http.Response, err error) ( return nil, fmt.Errorf("Could not decode JSON response: %v", dErr) } - rootNode := "extension_schema" + const rootNode = "extension_schema" + t, nodeOK := target[rootNode] if !nodeOK { return nil, fmt.Errorf("JSON response does not have %s field", rootNode) diff --git a/vendor/github.com/PagerDuty/go-pagerduty/incident.go b/vendor/github.com/PagerDuty/go-pagerduty/incident.go index 75da7e6..b6f8d85 100644 --- a/vendor/github.com/PagerDuty/go-pagerduty/incident.go +++ b/vendor/github.com/PagerDuty/go-pagerduty/incident.go @@ -1,7 +1,7 @@ package pagerduty import ( - "encoding/json" + "context" "fmt" "github.com/google/go-querystring/query" @@ -39,7 +39,7 @@ type Priority struct { Description string `json:"description,omitempty"` } -// Resolve reason is the data structure describing the reason an incident was resolved +// ResolveReason is the data structure describing the reason an incident was resolved type ResolveReason struct { Type string `json:"type,omitempty"` Incident APIObject `json:"incident"` @@ -51,35 +51,57 @@ type IncidentBody struct { Details string `json:"details,omitempty"` } +// Assignee is an individual assigned to an incident. type Assignee struct { Assignee APIObject `json:"assignee"` } +// Occurrence is the data around whether this is a reocurring issue. +type Occurrence struct { + Count uint `json:"count,omitempty"` + Frequency uint `json:"frequency,omitempty"` + Category string `json:"category,omitempty"` + Since string `json:"since,omitempty"` + Until string `json:"until,omitempty"` +} + +// FirstTriggerLogEntry is the first LogEntry +type FirstTriggerLogEntry struct { + CommonLogEntryField + Incident APIObject `json:"incident,omitempty"` +} + // Incident is a normalized, de-duplicated event generated by a PagerDuty integration. type Incident struct { APIObject - IncidentNumber uint `json:"incident_number,omitempty"` - Title string `json:"title,omitempty"` - Description string `json:"description,omitempty"` - CreatedAt string `json:"created_at,omitempty"` - PendingActions []PendingAction `json:"pending_actions,omitempty"` - IncidentKey string `json:"incident_key,omitempty"` - Service APIObject `json:"service,omitempty"` - Assignments []Assignment `json:"assignments,omitempty"` - Acknowledgements []Acknowledgement `json:"acknowledgements,omitempty"` - LastStatusChangeAt string `json:"last_status_change_at,omitempty"` - LastStatusChangeBy APIObject `json:"last_status_change_by,omitempty"` - FirstTriggerLogEntry APIObject `json:"first_trigger_log_entry,omitempty"` - EscalationPolicy APIObject `json:"escalation_policy,omitempty"` - Teams []APIObject `json:"teams,omitempty"` - Priority *Priority `json:"priority,omitempty"` - Urgency string `json:"urgency,omitempty"` - Status string `json:"status,omitempty"` - Id string `json:"id,omitempty"` - ResolveReason ResolveReason `json:"resolve_reason,omitempty"` - AlertCounts AlertCounts `json:"alert_counts,omitempty"` - Body IncidentBody `json:"body,omitempty"` - IsMergeable bool `json:"is_mergeable,omitempty"` + IncidentNumber uint `json:"incident_number,omitempty"` + Title string `json:"title,omitempty"` + Description string `json:"description,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + PendingActions []PendingAction `json:"pending_actions,omitempty"` + IncidentKey string `json:"incident_key,omitempty"` + Service APIObject `json:"service,omitempty"` + Assignments []Assignment `json:"assignments,omitempty"` + Acknowledgements []Acknowledgement `json:"acknowledgements,omitempty"` + LastStatusChangeAt string `json:"last_status_change_at,omitempty"` + LastStatusChangeBy APIObject `json:"last_status_change_by,omitempty"` + FirstTriggerLogEntry FirstTriggerLogEntry `json:"first_trigger_log_entry,omitempty"` + EscalationPolicy APIObject `json:"escalation_policy,omitempty"` + Teams []APIObject `json:"teams,omitempty"` + Priority *Priority `json:"priority,omitempty"` + Urgency string `json:"urgency,omitempty"` + Status string `json:"status,omitempty"` + ResolveReason ResolveReason `json:"resolve_reason,omitempty"` + AlertCounts AlertCounts `json:"alert_counts,omitempty"` + Body IncidentBody `json:"body,omitempty"` + IsMergeable bool `json:"is_mergeable,omitempty"` + ConferenceBridge *ConferenceBridge `json:"conference_bridge,omitempty"` + AssignedVia string `json:"assigned_via,omitempty"` + Occurrence *Occurrence `json:"occurrence,omitempty"` + IncidentResponders []IncidentResponders `json:"incidents_responders,omitempty"` + ResponderRequests []ResponderRequest `json:"responder_requests,omitempty"` + ResolvedAt string `json:"resolved_at,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` } // ListIncidentsResponse is the response structure when calling the ListIncident API endpoint. @@ -90,7 +112,25 @@ type ListIncidentsResponse struct { // ListIncidentsOptions is the structure used when passing parameters to the ListIncident API endpoint. type ListIncidentsOptions struct { - APIListObject + // Limit is the pagination parameter that limits the number of results per + // page. PagerDuty defaults this value to 25 if omitted, and sets an upper + // bound of 100. + Limit uint `url:"limit,omitempty"` + + // Offset is the pagination parameter that specifies the offset at which to + // start pagination results. When trying to request the next page of + // results, the new Offset value should be currentOffset + Limit. + Offset uint `url:"offset,omitempty"` + + // Total is the pagination parameter to request that the API return the + // total count of items in the response. If this field is omitted or set to + // false, the total number of results will not be sent back from the PagerDuty API. + // + // Setting this to true will slow down the API response times, and so it's + // recommended to omit it unless you've a specific reason for wanting the + // total count of items in the collection. + Total bool `url:"total,omitempty"` + Since string `url:"since,omitempty"` Until string `url:"until,omitempty"` DateRange string `url:"date_range,omitempty"` @@ -105,18 +145,44 @@ type ListIncidentsOptions struct { Includes []string `url:"include,omitempty,brackets"` } +// ConferenceBridge is a struct for the conference_bridge object on an incident +type ConferenceBridge struct { + // ConferenceNumber is the phone number of the conference call for the + // conference bridge. Phone numbers should be formatted like + // +1 415-555-1212,,,,1234#, where a comma (,) represents a one-second + // wait and pound (#) completes access code input. + ConferenceNumber string `json:"conference_number,omitempty"` + + // ConferenceURL is the URL for the conference bridge. This could be a link + // to a video conference or Slack channel. + ConferenceURL string `json:"conference_url,omitempty"` +} + // ListIncidents lists existing incidents. +// +// Deprecated: Use ListIncidentsWithContext instead. func (c *Client) ListIncidents(o ListIncidentsOptions) (*ListIncidentsResponse, error) { + return c.ListIncidentsWithContext(context.Background(), o) +} + +// ListIncidentsWithContext lists existing incidents. +func (c *Client) ListIncidentsWithContext(ctx context.Context, o ListIncidentsOptions) (*ListIncidentsResponse, error) { v, err := query.Values(o) if err != nil { return nil, err } - resp, err := c.get("/incidents?" + v.Encode()) + + resp, err := c.get(ctx, "/incidents?"+v.Encode(), nil) if err != nil { return nil, err } + var result ListIncidentsResponse - return &result, c.decodeJSON(resp, &result) + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil } // createIncidentResponse is returned from the API when creating a response. @@ -126,22 +192,44 @@ type createIncidentResponse struct { // CreateIncidentOptions is the structure used when POSTing to the CreateIncident API endpoint. type CreateIncidentOptions struct { - Type string `json:"type"` - Title string `json:"title"` - Service *APIReference `json:"service"` - Priority *APIReference `json:"priority"` - Urgency string `json:"urgency,omitempty"` - IncidentKey string `json:"incident_key,omitempty"` - Body *APIDetails `json:"body,omitempty"` - EscalationPolicy *APIReference `json:"escalation_policy,omitempty"` - Assignments []Assignee `json:"assignments,omitempty"` + // Type is the type of API object this is. + // + // Deprecated: Because the Type field can only have the value of "incident" + // when creating an incident, the CreateIncident* methods always set this + // value to "incident". Any other value will be overwritten. This will be + // removed in v2.0.0. + Type string `json:"type"` + Title string `json:"title"` + Service *APIReference `json:"service"` + Priority *APIReference `json:"priority"` + Urgency string `json:"urgency,omitempty"` + IncidentKey string `json:"incident_key,omitempty"` + Body *APIDetails `json:"body,omitempty"` + EscalationPolicy *APIReference `json:"escalation_policy,omitempty"` + Assignments []Assignee `json:"assignments,omitempty"` + ConferenceBridge *ConferenceBridge `json:"conference_bridge,omitempty"` } // ManageIncidentsOptions is the structure used when PUTing updates to incidents to the ManageIncidents func type ManageIncidentsOptions struct { - ID string `json:"id"` - Type string `json:"type"` - Status string `json:"status"` + ID string `json:"id"` + + // Type is the type of API object this is. + // + // Deprecated: Because the Type field can only have the value of "incident" + // or "incident_reference" when managing an incident, the CreateIncident* + // methods always set this value to "incident" because this struct is not an + // incident_reference. Any other value will be overwritten. This will be + // removed in v2.0.0. + Type string `json:"type"` + Status string `json:"status,omitempty"` + Title string `json:"title,omitempty"` + Priority *APIReference `json:"priority,omitempty"` + Assignments []Assignee `json:"assignments,omitempty"` + EscalationLevel uint `json:"escalation_level,omitempty"` + EscalationPolicy *APIReference `json:"escalation_policy,omitempty"` + Resolution string `json:"resolution,omitempty"` + ConferenceBridge *ConferenceBridge `json:"conference_bridge,omitempty"` } // MergeIncidentsOptions is the structure used when merging incidents with MergeIncidents func @@ -150,70 +238,132 @@ type MergeIncidentsOptions struct { Type string `json:"type"` } -// CreateIncident creates an incident synchronously without a corresponding event from a monitoring service. +// CreateIncident creates an incident synchronously without a corresponding +// event from a monitoring service. +// +// Deprecated: Use CreateIncidentWithContext instead. func (c *Client) CreateIncident(from string, o *CreateIncidentOptions) (*Incident, error) { - headers := make(map[string]string) - headers["From"] = from - data := make(map[string]*CreateIncidentOptions) - data["incident"] = o - resp, e := c.post("/incidents", data, &headers) - if e != nil { - return nil, e + return c.CreateIncidentWithContext(context.Background(), from, o) +} + +// CreateIncidentWithContext creates an incident synchronously without a +// corresponding event from a monitoring service. +func (c *Client) CreateIncidentWithContext(ctx context.Context, from string, o *CreateIncidentOptions) (*Incident, error) { + h := map[string]string{ + "From": from, + } + + // see: https://github.com/PagerDuty/go-pagerduty/issues/390 + o.Type = "incident" + + d := map[string]*CreateIncidentOptions{ + "incident": o, + } + + resp, err := c.post(ctx, "/incidents", d, h) + if err != nil { + return nil, err } var ii createIncidentResponse - e = json.NewDecoder(resp.Body).Decode(&ii) - if e != nil { - return nil, e + if err = c.decodeJSON(resp, &ii); err != nil { + return nil, err } return &ii.Incident, nil } -// ManageIncidents acknowledges, resolves, escalates, or reassigns one or more incidents. +// ManageIncidents acknowledges, resolves, escalates, or reassigns one or more +// incidents. +// +// Deprecated: Use ManageIncidentsWithContext instead. func (c *Client) ManageIncidents(from string, incidents []ManageIncidentsOptions) (*ListIncidentsResponse, error) { - data := make(map[string][]ManageIncidentsOptions) - headers := make(map[string]string) - headers["From"] = from - data["incidents"] = incidents + return c.ManageIncidentsWithContext(context.Background(), from, incidents) +} + +// ManageIncidentsWithContext acknowledges, resolves, escalates, or reassigns +// one or more incidents. +func (c *Client) ManageIncidentsWithContext(ctx context.Context, from string, incidents []ManageIncidentsOptions) (*ListIncidentsResponse, error) { + // see: https://github.com/PagerDuty/go-pagerduty/issues/390 + for i := range incidents { + incidents[i].Type = "incident" + } + + d := map[string][]ManageIncidentsOptions{ + "incidents": incidents, + } - resp, err := c.put("/incidents", data, &headers) + h := map[string]string{ + "From": from, + } + + resp, err := c.put(ctx, "/incidents", d, h) if err != nil { return nil, err } + var result ListIncidentsResponse - return &result, c.decodeJSON(resp, &result) + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil } -// MergeIncidents a list of source incidents into a specified incident. +// MergeIncidents merges a list of source incidents into a specified incident. +// +// Deprecated: Use MergeIncidentsWithContext instead. func (c *Client) MergeIncidents(from string, id string, sourceIncidents []MergeIncidentsOptions) (*Incident, error) { - r := make(map[string][]MergeIncidentsOptions) - r["source_incidents"] = sourceIncidents - headers := make(map[string]string) - headers["From"] = from + return c.MergeIncidentsWithContext(context.Background(), from, id, sourceIncidents) +} + +// MergeIncidentsWithContext merges a list of source incidents into a specified incident. +func (c *Client) MergeIncidentsWithContext(ctx context.Context, from, id string, sourceIncidents []MergeIncidentsOptions) (*Incident, error) { + d := map[string][]MergeIncidentsOptions{ + "source_incidents": sourceIncidents, + } - resp, err := c.put("/incidents/"+id+"/merge", r, &headers) + h := map[string]string{ + "From": from, + } + + resp, err := c.put(ctx, "/incidents/"+id+"/merge", d, h) if err != nil { return nil, err } + var result createIncidentResponse - return &result.Incident, c.decodeJSON(resp, &result) + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result.Incident, nil } // GetIncident shows detailed information about an incident. +// +// Deprecated: Use GetIncidentWithContext instead. func (c *Client) GetIncident(id string) (*Incident, error) { - resp, err := c.get("/incidents/" + id) + return c.GetIncidentWithContext(context.Background(), id) +} + +// GetIncidentWithContext shows detailed information about an incident. +func (c *Client) GetIncidentWithContext(ctx context.Context, id string) (*Incident, error) { + resp, err := c.get(ctx, "/incidents/"+id, nil) if err != nil { return nil, err } + var result map[string]Incident if err := c.decodeJSON(resp, &result); err != nil { return nil, err } + i, ok := result["incident"] if !ok { return nil, fmt.Errorf("JSON response does not have incident field") } + return &i, nil } @@ -231,19 +381,29 @@ type CreateIncidentNoteResponse struct { } // ListIncidentNotes lists existing notes for the specified incident. +// +// Deprecated: Use ListIncidentNotesWithContext instead. func (c *Client) ListIncidentNotes(id string) ([]IncidentNote, error) { - resp, err := c.get("/incidents/" + id + "/notes") + return c.ListIncidentNotesWithContext(context.Background(), id) +} + +// ListIncidentNotesWithContext lists existing notes for the specified incident. +func (c *Client) ListIncidentNotesWithContext(ctx context.Context, id string) ([]IncidentNote, error) { + resp, err := c.get(ctx, "/incidents/"+id+"/notes", nil) if err != nil { return nil, err } + var result map[string][]IncidentNote if err := c.decodeJSON(resp, &result); err != nil { return nil, err } + notes, ok := result["notes"] if !ok { return nil, fmt.Errorf("JSON response does not have notes field") } + return notes, nil } @@ -261,38 +421,107 @@ type IncidentAlert struct { Integration APIObject `json:"integration,omitempty"` } +// IncidentAlertResponse is the response of a sincle incident alert +type IncidentAlertResponse struct { + IncidentAlert *IncidentAlert `json:"alert,omitempty"` +} + +// IncidentAlertList is the generic structure of a list of alerts +type IncidentAlertList struct { + Alerts []IncidentAlert `json:"alerts,omitempty"` +} + // ListAlertsResponse is the response structure when calling the ListAlert API endpoint. type ListAlertsResponse struct { APIListObject Alerts []IncidentAlert `json:"alerts,omitempty"` } -// ListIncidentAlerts lists existing alerts for the specified incident. +// ListIncidentAlertsOptions is the structure used when passing parameters to the ListIncidentAlerts API endpoint. +type ListIncidentAlertsOptions struct { + // Limit is the pagination parameter that limits the number of results per + // page. PagerDuty defaults this value to 25 if omitted, and sets an upper + // bound of 100. + Limit uint `url:"limit,omitempty"` + + // Offset is the pagination parameter that specifies the offset at which to + // start pagination results. When trying to request the next page of + // results, the new Offset value should be currentOffset + Limit. + Offset uint `url:"offset,omitempty"` + + // Total is the pagination parameter to request that the API return the + // total count of items in the response. If this field is omitted or set to + // false, the total number of results will not be sent back from the PagerDuty API. + // + // Setting this to true will slow down the API response times, and so it's + // recommended to omit it unless you've a specific reason for wanting the + // total count of items in the collection. + Total bool `url:"total,omitempty"` + + Statuses []string `url:"statuses,omitempty,brackets"` + SortBy string `url:"sort_by,omitempty"` + Includes []string `url:"include,omitempty,brackets"` +} + +// ListIncidentAlerts lists existing alerts for the specified incident. It's +// recommended to use ListIncidentAlertsWithContext instead. func (c *Client) ListIncidentAlerts(id string) (*ListAlertsResponse, error) { - resp, err := c.get("/incidents/" + id + "/alerts") + return c.ListIncidentAlertsWithContext(context.Background(), id, ListIncidentAlertsOptions{}) +} + +// ListIncidentAlertsWithOpts lists existing alerts for the specified incident. +// +// Deprecated: Use ListIncidentAlertsWithContext instead. +func (c *Client) ListIncidentAlertsWithOpts(id string, o ListIncidentAlertsOptions) (*ListAlertsResponse, error) { + return c.ListIncidentAlertsWithContext(context.Background(), id, o) +} + +// ListIncidentAlertsWithContext lists existing alerts for the specified +// incident. If you don't want to filter any of the results, pass in an empty +// ListIncidentAlertOptions. +func (c *Client) ListIncidentAlertsWithContext(ctx context.Context, id string, o ListIncidentAlertsOptions) (*ListAlertsResponse, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + + resp, err := c.get(ctx, "/incidents/"+id+"/alerts?"+v.Encode(), nil) if err != nil { return nil, err } var result ListAlertsResponse - return &result, c.decodeJSON(resp, &result) + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, err } // CreateIncidentNoteWithResponse creates a new note for the specified incident. +// +// Deprecated: Use CreateIncidentNoteWithContext instead. func (c *Client) CreateIncidentNoteWithResponse(id string, note IncidentNote) (*IncidentNote, error) { - data := make(map[string]IncidentNote) - headers := make(map[string]string) - headers["From"] = note.User.Summary + return c.CreateIncidentNoteWithContext(context.Background(), id, note) +} - data["note"] = note - resp, err := c.post("/incidents/"+id+"/notes", data, &headers) +// CreateIncidentNoteWithContext creates a new note for the specified incident. +func (c *Client) CreateIncidentNoteWithContext(ctx context.Context, id string, note IncidentNote) (*IncidentNote, error) { + d := map[string]IncidentNote{ + "note": note, + } + + h := map[string]string{ + "From": note.User.Summary, + } + + resp, err := c.post(ctx, "/incidents/"+id+"/notes", d, h) if err != nil { return nil, err } - var result CreateIncidentNoteResponse - err = json.NewDecoder(resp.Body).Decode(&result) - if err != nil { + var result CreateIncidentNoteResponse + if err = c.decodeJSON(resp, &result); err != nil { return nil, err } @@ -300,28 +529,38 @@ func (c *Client) CreateIncidentNoteWithResponse(id string, note IncidentNote) (* } // CreateIncidentNote creates a new note for the specified incident. -// DEPRECATED: please use CreateIncidentNoteWithResponse going forward +// +// Deprecated: Use CreateIncidentNoteWithContext instead. func (c *Client) CreateIncidentNote(id string, note IncidentNote) error { data := make(map[string]IncidentNote) headers := make(map[string]string) headers["From"] = note.User.Summary - data["note"] = note - _, err := c.post("/incidents/"+id+"/notes", data, &headers) + _, err := c.post(context.Background(), "/incidents/"+id+"/notes", data, headers) return err } -// SnoozeIncidentSnoozeIncidentWithResponse sets an incident to not alert for a specified period of time. +// SnoozeIncidentWithResponse sets an incident to not alert for a specified +// period of time. +// +// Deprecated: Use SnoozeIncidentWithContext instead. func (c *Client) SnoozeIncidentWithResponse(id string, duration uint) (*Incident, error) { - data := make(map[string]uint) - data["duration"] = duration - resp, err := c.post("/incidents/"+id+"/snooze", data, nil) + return c.SnoozeIncidentWithContext(context.Background(), id, duration) +} + +// SnoozeIncidentWithContext sets an incident to not alert for a specified period of time. +func (c *Client) SnoozeIncidentWithContext(ctx context.Context, id string, duration uint) (*Incident, error) { + d := map[string]uint{ + "duration": duration, + } + + resp, err := c.post(ctx, "/incidents/"+id+"/snooze", d, nil) if err != nil { return nil, err } + var result createIncidentResponse - err = json.NewDecoder(resp.Body).Decode(&result) - if err != nil { + if err = c.decodeJSON(resp, &result); err != nil { return nil, err } @@ -329,11 +568,12 @@ func (c *Client) SnoozeIncidentWithResponse(id string, duration uint) (*Incident } // SnoozeIncident sets an incident to not alert for a specified period of time. -// DEPRECATED: please use SnoozeIncidentWithResponse going forward +// +// Deprecated: Use SnoozeIncidentWithContext instead. func (c *Client) SnoozeIncident(id string, duration uint) error { data := make(map[string]uint) data["duration"] = duration - _, err := c.post("/incidents/"+id+"/snooze", data, nil) + _, err := c.post(context.Background(), "/incidents/"+id+"/snooze", data, nil) return err } @@ -345,39 +585,58 @@ type ListIncidentLogEntriesResponse struct { // ListIncidentLogEntriesOptions is the structure used when passing parameters to the ListIncidentLogEntries API endpoint. type ListIncidentLogEntriesOptions struct { - APIListObject + // Limit is the pagination parameter that limits the number of results per + // page. PagerDuty defaults this value to 25 if omitted, and sets an upper + // bound of 100. + Limit uint `url:"limit,omitempty"` + + // Offset is the pagination parameter that specifies the offset at which to + // start pagination results. When trying to request the next page of + // results, the new Offset value should be currentOffset + Limit. + Offset uint `url:"offset,omitempty"` + + // Total is the pagination parameter to request that the API return the + // total count of items in the response. If this field is omitted or set to + // false, the total number of results will not be sent back from the PagerDuty API. + // + // Setting this to true will slow down the API response times, and so it's + // recommended to omit it unless you've a specific reason for wanting the + // total count of items in the collection. + Total bool `url:"total,omitempty"` + Includes []string `url:"include,omitempty,brackets"` IsOverview bool `url:"is_overview,omitempty"` TimeZone string `url:"time_zone,omitempty"` + Since string `url:"since,omitempty"` + Until string `url:"until,omitempty"` } // ListIncidentLogEntries lists existing log entries for the specified incident. +// +// Deprecated: Use ListIncidentLogEntriesWithContext instead. func (c *Client) ListIncidentLogEntries(id string, o ListIncidentLogEntriesOptions) (*ListIncidentLogEntriesResponse, error) { + return c.ListIncidentLogEntriesWithContext(context.Background(), id, o) +} + +// ListIncidentLogEntriesWithContext lists existing log entries for the +// specified incident. +func (c *Client) ListIncidentLogEntriesWithContext(ctx context.Context, id string, o ListIncidentLogEntriesOptions) (*ListIncidentLogEntriesResponse, error) { v, err := query.Values(o) if err != nil { return nil, err } - resp, err := c.get("/incidents/" + id + "/log_entries?" + v.Encode()) + + resp, err := c.get(ctx, "/incidents/"+id+"/log_entries?"+v.Encode(), nil) if err != nil { return nil, err } - var result ListIncidentLogEntriesResponse - return &result, c.decodeJSON(resp, &result) -} -// Alert is a list of all of the alerts that happened to an incident. -type Alert struct { - APIObject - Service APIObject `json:"service,omitempty"` - CreatedAt string `json:"created_at,omitempty"` - Status string `json:"status,omitempty"` - AlertKey string `json:"alert_key,omitempty"` - Incident APIObject `json:"incident,omitempty"` -} + var result ListIncidentLogEntriesResponse + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } -type ListAlertResponse struct { - APIListObject - Alerts []Alert `json:"alerts,omitempty"` + return &result, nil } // IncidentResponders contains details about responders to an incident. @@ -391,7 +650,8 @@ type IncidentResponders struct { RequestedAt string `json:"requested_at"` } -// ResponderRequestResponse +// ResponderRequestResponse is the response from the API when requesting someone +// respond to an incident. type ResponderRequestResponse struct { ResponderRequest ResponderRequest `json:"responder_request"` } @@ -399,44 +659,220 @@ type ResponderRequestResponse struct { // ResponderRequestTarget specifies an individual target for the responder request. type ResponderRequestTarget struct { APIObject - Responders IncidentResponders `json:"incident_responders"` + Responders []IncidentResponders `json:"incidents_responders,omitempty"` } -// ResponderRequestTargets is a wrapper for a ResponderRequestTarget. -type ResponderRequestTargets struct { +// ResponderRequestTargetWrapper is a wrapper for a ResponderRequestTarget. +type ResponderRequestTargetWrapper struct { Target ResponderRequestTarget `json:"responder_request_target"` } // ResponderRequestOptions defines the input options for the Create Responder function. type ResponderRequestOptions struct { - From string `json:"-"` - Message string `json:"message"` - RequesterID string `json:"request_id"` - Targets []ResponderRequestTarget `json:"responder_request_targets"` + From string `json:"-"` + Message string `json:"message"` + RequesterID string `json:"requester_id"` + Targets []ResponderRequestTargetWrapper `json:"responder_request_targets"` } // ResponderRequest contains the API structure for an incident responder request. type ResponderRequest struct { - Incident Incident `json:"incident"` - Requester User `json:"requester,omitempty"` - RequestedAt string `json:"request_at,omitempty"` - Message string `json:"message,omitempty"` - Targets ResponderRequestTargets `json:"responder_request_targets"` + Incident Incident `json:"incident"` + Requester User `json:"requester,omitempty"` + RequestedAt string `json:"request_at,omitempty"` + Message string `json:"message,omitempty"` + Targets []ResponderRequestTargetWrapper `json:"responder_request_targets"` } // ResponderRequest will submit a request to have a responder join an incident. +// +// Deprecated: Use ResponderRequestWithContext instead. func (c *Client) ResponderRequest(id string, o ResponderRequestOptions) (*ResponderRequestResponse, error) { - headers := make(map[string]string) - headers["From"] = o.From + return c.ResponderRequestWithContext(context.Background(), id, o) +} + +// ResponderRequestWithContext will submit a request to have a responder join an incident. +func (c *Client) ResponderRequestWithContext(ctx context.Context, id string, o ResponderRequestOptions) (*ResponderRequestResponse, error) { + h := map[string]string{ + "From": o.From, + } + + resp, err := c.post(ctx, "/incidents/"+id+"/responder_requests", o, h) + if err != nil { + return nil, err + } + + var result ResponderRequestResponse + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil +} + +// GetIncidentAlert gets the alert that triggered the incident. +// +// Deprecated: Use GetIncidentAlertWithContext instead. +func (c *Client) GetIncidentAlert(incidentID, alertID string) (*IncidentAlertResponse, error) { + return c.GetIncidentAlertWithContext(context.Background(), incidentID, alertID) +} + +// GetIncidentAlertWithContext gets the alert that triggered the incident. +func (c *Client) GetIncidentAlertWithContext(ctx context.Context, incidentID, alertID string) (*IncidentAlertResponse, error) { + resp, err := c.get(ctx, "/incidents/"+incidentID+"/alerts/"+alertID, nil) + if err != nil { + return nil, err + } + + var result IncidentAlertResponse + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil +} + +// ManageIncidentAlerts allows you to manage the alerts of an incident. +func (c *Client) ManageIncidentAlerts(ctx context.Context, incidentID, from string, alerts *IncidentAlertList) (*ListAlertsResponse, error) { + h := map[string]string{ + "From": from, + } + + resp, err := c.put(ctx, "/incidents/"+incidentID+"/alerts/", alerts, h) + if err != nil { + return nil, err + } + + var result ListAlertsResponse + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil +} + +// IncidentStatusUpdate is a status update for the specified incident. +type IncidentStatusUpdate struct { + ID string `json:"id"` + Message string `json:"message"` + CreatedAt string `json:"created_at"` + Sender APIObject `json:"sender"` +} + +// CreateIncidentStatusUpdate creates a new status update for the specified incident. +func (c *Client) CreateIncidentStatusUpdate(ctx context.Context, id string, from string, message string) (IncidentStatusUpdate, error) { + d := map[string]string{ + "message": message, + } - resp, err := c.post("/incidents/"+id+"/responder_requests", o, &headers) + h := map[string]string{ + "From": from, + } + + resp, err := c.post(ctx, "/incidents/"+id+"/status_updates", d, h) if err != nil { + return IncidentStatusUpdate{}, err + } + + var result struct { + IncidentStatusUpdate IncidentStatusUpdate `json:"status_update"` + } + if err = c.decodeJSON(resp, &result); err != nil { + return IncidentStatusUpdate{}, err + } + + return result.IncidentStatusUpdate, nil +} + +// IncidentNotificationSubscriber is a Notification Subscriber on a Incident. +type IncidentNotificationSubscriber struct { + SubscriberID string `json:"subscriber_id"` + SubscriberType string `json:"subscriber_type"` +} + +// IncidentNotificationSubscriptionWithContext contains extra context returned from the API. +type IncidentNotificationSubscriptionWithContext struct { + IncidentNotificationSubscriber + HasIndirectSubscription bool `json:"has_indirect_subscription,omitempty"` + SubscribedVia []IncidentNotificationSubscriberVia `json:"subscribed_via,omitempty"` + SubscribableID string `json:"subscribable_id,omitempty"` + SubscribableType string `json:"subscribable_type,omitempty"` + AccountID string `json:"account_id,omitempty"` + Result string `json:"result,omitempty"` +} +type IncidentNotificationSubscriberVia struct { + ID string `json:"id"` + Type string `json:"type"` +} + +// ListIncidentNotificationSubscribersResponse is the response structure when calling the ListNotificationSubscribers API endpoint. +type ListIncidentNotificationSubscribersResponse struct { + APIListObject + Subscribers []IncidentNotificationSubscriptionWithContext `json:"subscribers,omitempty"` + AccountID string `json:"account_id,omitempty"` +} + +// AddIncidentNotificationSubscribersResponse is the response structure when calling the AddNotificationSubscribers API endpoint. +type AddIncidentNotificationSubscribersResponse struct { + Subscriptions []IncidentNotificationSubscriptionWithContext `json:"subscriptions,omitempty"` +} + +// RemoveIncidentNotificationSubscribersResponse is the response structure when calling the RemoveNotificationSubscriber API endpoint. +type RemoveIncidentNotificationSubscribersResponse struct { + DeleteCount uint `json:"deleted_count"` + UnauthorizedCount uint `json:"unauthorized_count"` + NonExistentCount uint `json:"non_existent_count"` +} + +// ListIncidentNotificationSubscribersWithContext lists notification subscribers for the specified incident. +func (c *Client) ListIncidentNotificationSubscribersWithContext(ctx context.Context, id string) (*ListIncidentNotificationSubscribersResponse, error) { + resp, err := c.get(ctx, "/incidents/"+id+"/status_updates/subscribers", nil) + if err != nil { + return nil, err + } + + var result ListIncidentNotificationSubscribersResponse + if err = c.decodeJSON(resp, &result); err != nil { return nil, err } - result := &ResponderRequestResponse{} - err = json.NewDecoder(resp.Body).Decode(result) - return result, err + return &result, nil } -/* TODO: Manage Alerts, Get Alert, Create Status Updates */ +// AddIncidentNotificationSubscribersWithContext adds notification subscribers for the specified incident. +func (c *Client) AddIncidentNotificationSubscribersWithContext(ctx context.Context, id string, subscribers []IncidentNotificationSubscriber) (*AddIncidentNotificationSubscribersResponse, error) { + d := map[string][]IncidentNotificationSubscriber{ + "subscribers": subscribers, + } + + resp, err := c.post(ctx, "/incidents/"+id+"/status_updates/subscribers", d, nil) + if err != nil { + return nil, err + } + + var result AddIncidentNotificationSubscribersResponse + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil +} + +// RemoveIncidentNotificationSubscribersWithContext removes notification subscribers for the specified incident. +func (c *Client) RemoveIncidentNotificationSubscribersWithContext(ctx context.Context, id string, subscribers []IncidentNotificationSubscriber) (*RemoveIncidentNotificationSubscribersResponse, error) { + d := map[string][]IncidentNotificationSubscriber{ + "subscribers": subscribers, + } + + resp, err := c.post(ctx, "/incidents/"+id+"/status_updates/unsubscribe", d, nil) + if err != nil { + return nil, err + } + + var result RemoveIncidentNotificationSubscribersResponse + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/license.go b/vendor/github.com/PagerDuty/go-pagerduty/license.go new file mode 100644 index 0000000..b07aaf1 --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/license.go @@ -0,0 +1,84 @@ +package pagerduty + +import ( + "context" + + "github.com/google/go-querystring/query" +) + +type License struct { + APIObject + Name string `json:"name"` + Description string `json:"description"` + ValidRoles []string `json:"valid_roles"` + RoleGroup string `json:"role_group"` + Summary string `json:"summary"` + CurrentValue int `json:"current_value"` + AllocationsAvailable int `json:"allocations_available"` +} + +type LicenseAllocated struct { + APIObject + Name string `json:"name"` + Description string `json:"description"` + ValidRoles []string `json:"valid_roles"` + RoleGroup string `json:"role_group"` + Summary string `json:"summary"` +} + +type LicenseAllocation struct { + AllocatedAt string `json:"allocated_at"` + User APIObject + License LicenseAllocated `json:"license"` +} + +type ListLicensesResponse struct { + Licenses []License `json:"licenses"` +} + +type ListLicenseAllocationsResponse struct { + APIListObject + LicenseAllocations []LicenseAllocation `json:"license_allocations"` +} + +type ListLicenseAllocationsOptions struct { + Limit int `url:"limit,omitempty"` + Offset int `url:"offset,omitempty"` +} + +func (c *Client) ListLicensesWithContext(ctx context.Context) (*ListLicensesResponse, error) { + + resp, err := c.get(ctx, "/licenses", nil) + if err != nil { + return nil, err + } + + var result ListLicensesResponse + err = c.decodeJSON(resp, &result) + if err != nil { + return nil, err + } + + return &result, nil +} + +func (c *Client) ListLicenseAllocationsWithContext(ctx context.Context, o ListLicenseAllocationsOptions) (*ListLicenseAllocationsResponse, error) { + + v, err := query.Values(o) + if err != nil { + return nil, err + } + + resp, err := c.get(ctx, "/license_allocations?"+v.Encode(), nil) + if err != nil { + return nil, err + } + + var result ListLicenseAllocationsResponse + err = c.decodeJSON(resp, &result) + if err != nil { + return nil, err + } + + return &result, nil +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/log_entry.go b/vendor/github.com/PagerDuty/go-pagerduty/log_entry.go index 1b5e2fc..c37fd99 100644 --- a/vendor/github.com/PagerDuty/go-pagerduty/log_entry.go +++ b/vendor/github.com/PagerDuty/go-pagerduty/log_entry.go @@ -1,6 +1,8 @@ package pagerduty import ( + "context" + "encoding/json" "fmt" "github.com/google/go-querystring/query" @@ -12,6 +14,7 @@ type Agent APIObject // Channel is the means by which the action was carried out. type Channel struct { Type string + Raw map[string]interface{} } // Context are to be included with the trigger such as links to graphs or images. @@ -23,17 +26,25 @@ type Context struct { Type string } +// CommonLogEntryField is the list of shared log entry between Incident and LogEntry +type CommonLogEntryField struct { + APIObject + CreatedAt string `json:"created_at,omitempty"` + Agent Agent `json:"agent,omitempty"` + Channel Channel `json:"channel,omitempty"` + Teams []Team `json:"teams,omitempty"` + Contexts []Context `json:"contexts,omitempty"` + AcknowledgementTimeout int `json:"acknowledgement_timeout"` + EventDetails map[string]string `json:"event_details,omitempty"` + Assignees []APIObject `json:"assignees,omitempty"` +} + // LogEntry is a list of all of the events that happened to an incident. type LogEntry struct { - APIObject - CreatedAt string `json:"created_at"` - Agent Agent - Channel Channel - Incident Incident - Teams []Team - Contexts []Context - AcknowledgementTimeout int `json:"acknowledgement_timeout"` - EventDetails map[string]string + CommonLogEntryField + Incident Incident `json:"incident"` + Service APIObject `json:"service"` + User APIObject `json:"user"` } // ListLogEntryResponse is the response data when calling the ListLogEntry API endpoint. @@ -44,26 +55,59 @@ type ListLogEntryResponse struct { // ListLogEntriesOptions is the data structure used when calling the ListLogEntry API endpoint. type ListLogEntriesOptions struct { - APIListObject - TimeZone string `url:"time_zone"` + // Limit is the pagination parameter that limits the number of results per + // page. PagerDuty defaults this value to 25 if omitted, and sets an upper + // bound of 100. + Limit uint `url:"limit,omitempty"` + + // Offset is the pagination parameter that specifies the offset at which to + // start pagination results. When trying to request the next page of + // results, the new Offset value should be currentOffset + Limit. + Offset uint `url:"offset,omitempty"` + + // Total is the pagination parameter to request that the API return the + // total count of items in the response. If this field is omitted or set to + // false, the total number of results will not be sent back from the PagerDuty API. + // + // Setting this to true will slow down the API response times, and so it's + // recommended to omit it unless you've a specific reason for wanting the + // total count of items in the collection. + Total bool `url:"total,omitempty"` + + TimeZone string `url:"time_zone,omitempty"` Since string `url:"since,omitempty"` Until string `url:"until,omitempty"` IsOverview bool `url:"is_overview,omitempty"` Includes []string `url:"include,omitempty,brackets"` + TeamIDs []string `url:"team_ids,omitempty,brackets"` } -// ListLogEntries lists all of the incident log entries across the entire account. +// ListLogEntries lists all of the incident log entries across the entire +// account. +// +// Deprecated: Use ListLogEntriesWithContext instead. func (c *Client) ListLogEntries(o ListLogEntriesOptions) (*ListLogEntryResponse, error) { + return c.ListLogEntriesWithContext(context.Background(), o) +} + +// ListLogEntriesWithContext lists all of the incident log entries across the entire account. +func (c *Client) ListLogEntriesWithContext(ctx context.Context, o ListLogEntriesOptions) (*ListLogEntryResponse, error) { v, err := query.Values(o) if err != nil { return nil, err } - resp, err := c.get("/log_entries?" + v.Encode()) + + resp, err := c.get(ctx, "/log_entries?"+v.Encode(), nil) if err != nil { return nil, err } + var result ListLogEntryResponse - return &result, c.decodeJSON(resp, &result) + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, err } // GetLogEntryOptions is the data structure used when calling the GetLogEntry API endpoint. @@ -73,22 +117,60 @@ type GetLogEntryOptions struct { } // GetLogEntry list log entries for the specified incident. +// +// Deprecated: Use GetLogEntryWithContext instead. func (c *Client) GetLogEntry(id string, o GetLogEntryOptions) (*LogEntry, error) { + return c.GetLogEntryWithContext(context.Background(), id, o) +} + +// GetLogEntryWithContext list log entries for the specified incident. +func (c *Client) GetLogEntryWithContext(ctx context.Context, id string, o GetLogEntryOptions) (*LogEntry, error) { v, err := query.Values(o) if err != nil { return nil, err } - resp, err := c.get("/log_entries/" + id + "?" + v.Encode()) + + resp, err := c.get(ctx, "/log_entries/"+id+"?"+v.Encode(), nil) if err != nil { return nil, err } + var result map[string]LogEntry if err := c.decodeJSON(resp, &result); err != nil { return nil, err } + le, ok := result["log_entry"] if !ok { return nil, fmt.Errorf("JSON response does not have log_entry field") } + return &le, nil } + +// UnmarshalJSON Expands the LogEntry.Channel object to parse out a raw value +func (c *Channel) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + ct, ok := raw["type"] + if ok { + c.Type = ct.(string) + c.Raw = raw + } + + return nil +} + +// MarshalJSON Expands the LogEntry.Channel object to correctly marshal it back +func (c *Channel) MarshalJSON() ([]byte, error) { + raw := map[string]interface{}{} + if c != nil && c.Type != "" { + for k, v := range c.Raw { + raw[k] = v + } + } + + return json.Marshal(raw) +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/maintenance_window.go b/vendor/github.com/PagerDuty/go-pagerduty/maintenance_window.go index 21e9061..5465f8d 100644 --- a/vendor/github.com/PagerDuty/go-pagerduty/maintenance_window.go +++ b/vendor/github.com/PagerDuty/go-pagerduty/maintenance_window.go @@ -1,6 +1,7 @@ package pagerduty import ( + "context" "fmt" "net/http" @@ -10,13 +11,13 @@ import ( // MaintenanceWindow is used to temporarily disable one or more services for a set period of time. type MaintenanceWindow struct { APIObject - SequenceNumber uint `json:"sequence_number,omitempty"` - StartTime string `json:"start_time"` - EndTime string `json:"end_time"` - Description string `json:"description"` - Services []APIObject `json:"services"` - Teams []APIListObject `json:"teams"` - CreatedBy APIListObject `json:"created_by"` + SequenceNumber uint `json:"sequence_number,omitempty"` + StartTime string `json:"start_time"` + EndTime string `json:"end_time"` + Description string `json:"description"` + Services []APIObject `json:"services"` + Teams []APIObject `json:"teams,omitempty"` + CreatedBy *APIObject `json:"created_by,omitempty"` } // ListMaintenanceWindowsResponse is the data structur returned from calling the ListMaintenanceWindows API endpoint. @@ -27,7 +28,25 @@ type ListMaintenanceWindowsResponse struct { // ListMaintenanceWindowsOptions is the data structure used when calling the ListMaintenanceWindows API endpoint. type ListMaintenanceWindowsOptions struct { - APIListObject + // Limit is the pagination parameter that limits the number of results per + // page. PagerDuty defaults this value to 25 if omitted, and sets an upper + // bound of 100. + Limit uint `url:"limit,omitempty"` + + // Offset is the pagination parameter that specifies the offset at which to + // start pagination results. When trying to request the next page of + // results, the new Offset value should be currentOffset + Limit. + Offset uint `url:"offset,omitempty"` + + // Total is the pagination parameter to request that the API return the + // total count of items in the response. If this field is omitted or set to + // false, the total number of results will not be sent back from the PagerDuty API. + // + // Setting this to true will slow down the API response times, and so it's + // recommended to omit it unless you've a specific reason for wanting the + // total count of items in the collection. + Total bool `url:"total,omitempty"` + Query string `url:"query,omitempty"` Includes []string `url:"include,omitempty,brackets"` TeamIDs []string `url:"team_ids,omitempty,brackets"` @@ -35,42 +54,83 @@ type ListMaintenanceWindowsOptions struct { Filter string `url:"filter,omitempty,brackets"` } -// ListMaintenanceWindows lists existing maintenance windows, optionally filtered by service and/or team, or whether they are from the past, present or future. +// ListMaintenanceWindows lists existing maintenance windows, optionally +// filtered by service and/or team, or whether they are from the past, present +// or future. +// +// Deprecated: Use ListMaintenanceWindowsWithContext instead. func (c *Client) ListMaintenanceWindows(o ListMaintenanceWindowsOptions) (*ListMaintenanceWindowsResponse, error) { + return c.ListMaintenanceWindowsWithContext(context.Background(), o) +} + +// ListMaintenanceWindowsWithContext lists existing maintenance windows, +// optionally filtered by service and/or team, or whether they are from the +// past, present or future. +func (c *Client) ListMaintenanceWindowsWithContext(ctx context.Context, o ListMaintenanceWindowsOptions) (*ListMaintenanceWindowsResponse, error) { v, err := query.Values(o) if err != nil { return nil, err } - resp, err := c.get("/maintenance_windows?" + v.Encode()) + + resp, err := c.get(ctx, "/maintenance_windows?"+v.Encode(), nil) if err != nil { return nil, err } + var result ListMaintenanceWindowsResponse - return &result, c.decodeJSON(resp, &result) + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil } -// CreateMaintenanceWindow creates a new maintenance window for the specified services. +// CreateMaintenanceWindow creates a new maintenance window for the specified +// services. +// +// Deprecated: Use CreateMaintenanceWindowWithContext instead. func (c *Client) CreateMaintenanceWindow(from string, o MaintenanceWindow) (*MaintenanceWindow, error) { - data := make(map[string]MaintenanceWindow) + return c.CreateMaintenanceWindowWithContext(context.Background(), from, o) +} + +// CreateMaintenanceWindowWithContext creates a new maintenance window for the specified services. +func (c *Client) CreateMaintenanceWindowWithContext(ctx context.Context, from string, o MaintenanceWindow) (*MaintenanceWindow, error) { o.Type = "maintenance_window" - data["maintenance_window"] = o - headers := make(map[string]string) + + d := map[string]MaintenanceWindow{ + "maintenance_window": o, + } + + var h map[string]string if from != "" { - headers["From"] = from + h = map[string]string{ + "From": from, + } } - resp, err := c.post("/maintenance_windows", data, &headers) + + resp, err := c.post(ctx, "/maintenance_windows", d, h) return getMaintenanceWindowFromResponse(c, resp, err) } // CreateMaintenanceWindows creates a new maintenance window for the specified services. -// Deprecated: Use `CreateMaintenanceWindow` instead. +// +// Deprecated: Use CreateMaintenanceWindowWithContext instead. func (c *Client) CreateMaintenanceWindows(o MaintenanceWindow) (*MaintenanceWindow, error) { - return c.CreateMaintenanceWindow("", o) + return c.CreateMaintenanceWindowWithContext(context.Background(), "", o) } -// DeleteMaintenanceWindow deletes an existing maintenance window if it's in the future, or ends it if it's currently on-going. +// DeleteMaintenanceWindow deletes an existing maintenance window if it's in the +// future, or ends it if it's currently on-going. +// +// Deprecated: Use DeleteMaintenanceWindowWithContext instead. func (c *Client) DeleteMaintenanceWindow(id string) error { - _, err := c.delete("/maintenance_windows/" + id) + return c.DeleteMaintenanceWindowWithContext(context.Background(), id) +} + +// DeleteMaintenanceWindowWithContext deletes an existing maintenance window if it's in the +// future, or ends it if it's currently on-going. +func (c *Client) DeleteMaintenanceWindowWithContext(ctx context.Context, id string) error { + _, err := c.delete(ctx, "/maintenance_windows/"+id) return err } @@ -80,18 +140,33 @@ type GetMaintenanceWindowOptions struct { } // GetMaintenanceWindow gets an existing maintenance window. +// +// Deprecated: Use GetMaintenanceWindowWithContext instead. func (c *Client) GetMaintenanceWindow(id string, o GetMaintenanceWindowOptions) (*MaintenanceWindow, error) { + return c.GetMaintenanceWindowWithContext(context.Background(), id, o) +} + +// GetMaintenanceWindowWithContext gets an existing maintenance window. +func (c *Client) GetMaintenanceWindowWithContext(ctx context.Context, id string, o GetMaintenanceWindowOptions) (*MaintenanceWindow, error) { v, err := query.Values(o) if err != nil { return nil, err } - resp, err := c.get("/maintenance_windows/" + id + "?" + v.Encode()) + + resp, err := c.get(ctx, "/maintenance_windows/"+id+"?"+v.Encode(), nil) return getMaintenanceWindowFromResponse(c, resp, err) } // UpdateMaintenanceWindow updates an existing maintenance window. +// +// Deprecated: Use UpdateMaintenanceWindowWithContext instead. func (c *Client) UpdateMaintenanceWindow(m MaintenanceWindow) (*MaintenanceWindow, error) { - resp, err := c.put("/maintenance_windows/"+m.ID, m, nil) + return c.UpdateMaintenanceWindowWithContext(context.Background(), m) +} + +// UpdateMaintenanceWindowWithContext updates an existing maintenance window. +func (c *Client) UpdateMaintenanceWindowWithContext(ctx context.Context, m MaintenanceWindow) (*MaintenanceWindow, error) { + resp, err := c.put(ctx, "/maintenance_windows/"+m.ID, m, nil) return getMaintenanceWindowFromResponse(c, resp, err) } @@ -99,14 +174,18 @@ func getMaintenanceWindowFromResponse(c *Client, resp *http.Response, err error) if err != nil { return nil, err } + var target map[string]MaintenanceWindow if dErr := c.decodeJSON(resp, &target); dErr != nil { return nil, fmt.Errorf("Could not decode JSON response: %v", dErr) } - rootNode := "maintenance_window" + + const rootNode = "maintenance_window" + t, nodeOK := target[rootNode] if !nodeOK { return nil, fmt.Errorf("JSON response does not have %s field", rootNode) } + return &t, nil } diff --git a/vendor/github.com/PagerDuty/go-pagerduty/notification.go b/vendor/github.com/PagerDuty/go-pagerduty/notification.go index cdd87c1..f1e6797 100644 --- a/vendor/github.com/PagerDuty/go-pagerduty/notification.go +++ b/vendor/github.com/PagerDuty/go-pagerduty/notification.go @@ -1,26 +1,48 @@ package pagerduty import ( + "context" + "github.com/google/go-querystring/query" ) // Notification is a message containing the details of the incident. type Notification struct { - ID string `json:"id"` - Type string - StartedAt string `json:"started_at"` - Address string - User APIObject + ID string `json:"id"` + Type string `json:"type"` + StartedAt string `json:"started_at"` + Address string `json:"address"` + User APIObject `json:"user"` + ConferenceAddress string `json:"conferenceAddress"` + Status string `json:"status"` } // ListNotificationOptions is the data structure used when calling the ListNotifications API endpoint. type ListNotificationOptions struct { - APIListObject + // Limit is the pagination parameter that limits the number of results per + // page. PagerDuty defaults this value to 25 if omitted, and sets an upper + // bound of 100. + Limit uint `url:"limit,omitempty"` + + // Offset is the pagination parameter that specifies the offset at which to + // start pagination results. When trying to request the next page of + // results, the new Offset value should be currentOffset + Limit. + Offset uint `url:"offset,omitempty"` + + // Total is the pagination parameter to request that the API return the + // total count of items in the response. If this field is omitted or set to + // false, the total number of results will not be sent back from the PagerDuty API. + // + // Setting this to true will slow down the API response times, and so it's + // recommended to omit it unless you've a specific reason for wanting the + // total count of items in the collection. + Total bool `url:"total,omitempty"` + TimeZone string `url:"time_zone,omitempty"` Since string `url:"since,omitempty"` Until string `url:"until,omitempty"` Filter string `url:"filter,omitempty"` - Includes []string `url:"include,omitempty"` + Includes []string `url:"include,omitempty,brackets"` } // ListNotificationsResponse is the data structure returned from the ListNotifications API endpoint. @@ -29,16 +51,33 @@ type ListNotificationsResponse struct { Notifications []Notification } -// ListNotifications lists notifications for a given time range, optionally filtered by type (sms_notification, email_notification, phone_notification, or push_notification). +// ListNotifications lists notifications for a given time range, optionally +// filtered by type (sms_notification, email_notification, phone_notification, +// or push_notification). +// +// Deprecated: Use ListNotificationsWithContext instead. func (c *Client) ListNotifications(o ListNotificationOptions) (*ListNotificationsResponse, error) { + return c.ListNotificationsWithContext(context.Background(), o) +} + +// ListNotificationsWithContext lists notifications for a given time range, +// optionally filtered by type (sms_notification, email_notification, +// phone_notification, or push_notification). +func (c *Client) ListNotificationsWithContext(ctx context.Context, o ListNotificationOptions) (*ListNotificationsResponse, error) { v, err := query.Values(o) if err != nil { return nil, err } - resp, err := c.get("/notifications?" + v.Encode()) + + resp, err := c.get(ctx, "/notifications?"+v.Encode(), nil) if err != nil { return nil, err } + var result ListNotificationsResponse - return &result, c.decodeJSON(resp, &result) + if err := c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil } diff --git a/vendor/github.com/PagerDuty/go-pagerduty/on_call.go b/vendor/github.com/PagerDuty/go-pagerduty/on_call.go index 537ef10..6d406fc 100644 --- a/vendor/github.com/PagerDuty/go-pagerduty/on_call.go +++ b/vendor/github.com/PagerDuty/go-pagerduty/on_call.go @@ -1,6 +1,8 @@ package pagerduty import ( + "context" + "github.com/google/go-querystring/query" ) @@ -22,7 +24,25 @@ type ListOnCallsResponse struct { // ListOnCallOptions is the data structure used when calling the ListOnCalls API endpoint. type ListOnCallOptions struct { - APIListObject + // Limit is the pagination parameter that limits the number of results per + // page. PagerDuty defaults this value to 25 if omitted, and sets an upper + // bound of 100. + Limit uint `url:"limit,omitempty"` + + // Offset is the pagination parameter that specifies the offset at which to + // start pagination results. When trying to request the next page of + // results, the new Offset value should be currentOffset + Limit. + Offset uint `url:"offset,omitempty"` + + // Total is the pagination parameter to request that the API return the + // total count of items in the response. If this field is omitted or set to + // false, the total number of results will not be sent back from the PagerDuty API. + // + // Setting this to true will slow down the API response times, and so it's + // recommended to omit it unless you've a specific reason for wanting the + // total count of items in the collection. + Total bool `url:"total,omitempty"` + TimeZone string `url:"time_zone,omitempty"` Includes []string `url:"include,omitempty,brackets"` UserIDs []string `url:"user_ids,omitempty,brackets"` @@ -34,15 +54,28 @@ type ListOnCallOptions struct { } // ListOnCalls list the on-call entries during a given time range. +// +// Deprecated: Use ListOnCallsWithContext instead. func (c *Client) ListOnCalls(o ListOnCallOptions) (*ListOnCallsResponse, error) { + return c.ListOnCallsWithContext(context.Background(), o) +} + +// ListOnCallsWithContext list the on-call entries during a given time range. +func (c *Client) ListOnCallsWithContext(ctx context.Context, o ListOnCallOptions) (*ListOnCallsResponse, error) { v, err := query.Values(o) if err != nil { return nil, err } - resp, err := c.get("/oncalls?" + v.Encode()) + + resp, err := c.get(ctx, "/oncalls?"+v.Encode(), nil) if err != nil { return nil, err } + var result ListOnCallsResponse - return &result, c.decodeJSON(resp, &result) + if err := c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil } diff --git a/vendor/github.com/PagerDuty/go-pagerduty/priorites.go b/vendor/github.com/PagerDuty/go-pagerduty/priorites.go index 69a33a2..83d2dd6 100644 --- a/vendor/github.com/PagerDuty/go-pagerduty/priorites.go +++ b/vendor/github.com/PagerDuty/go-pagerduty/priorites.go @@ -1,32 +1,75 @@ package pagerduty import ( - "encoding/json" + "context" + + "github.com/google/go-querystring/query" ) -// PriorityProperty is a single priorty object returned from the Priorities endpoint -type PriorityProperty struct { - APIObject - Name string `json:"name"` - Description string `json:"description"` -} +// PriorityProperty is the original type name and is retained as an alias for API +// compatibility. +// +// Deprecated: Use type Priority instead; will be removed in V2 +type PriorityProperty = Priority -type Priorities struct { +// ListPrioritiesResponse repreents the API response from PagerDuty when listing +// the configured priorities. +type ListPrioritiesResponse struct { APIListObject - Priorities []PriorityProperty `json:"priorities"` + Priorities []Priority `json:"priorities"` } -// ListPriorities lists existing priorities -func (c *Client) ListPriorities() (*Priorities, error) { - resp, e := c.get("/priorities") - if e != nil { - return nil, e +// Priorities is the original type name and is retained as an alias for API +// compatibility. +// +// Deprecated: Use type ListPrioritiesResponse instead; will be removed in V2 +type Priorities = ListPrioritiesResponse + +// ListPrioritiesOptions is the data structure used when calling the +// ListPriorities API endpoint. +type ListPrioritiesOptions struct { + // Limit is the pagination parameter that limits the number of results per + // page. PagerDuty defaults this value to 25 if omitted, and sets an upper + // bound of 100. + Limit uint `url:"limit,omitempty"` + + // Offset is the pagination parameter that specifies the offset at which to + // start pagination results. When trying to request the next page of + // results, the new Offset value should be currentOffset + Limit. + Offset uint `url:"offset,omitempty"` + + // Total is the pagination parameter to request that the API return the + // total count of items in the response. If this field is omitted or set to + // false, the total number of results will not be sent back from the PagerDuty API. + // + // Setting this to true will slow down the API response times, and so it's + // recommended to omit it unless you've a specific reason for wanting the + // total count of items in the collection. + Total bool `url:"total,omitempty"` +} + +// ListPriorities lists existing priorities. +// +// Deprecated: Use ListPrioritiesWithContext instead. +func (c *Client) ListPriorities() (*ListPrioritiesResponse, error) { + return c.ListPrioritiesWithContext(context.Background(), ListPrioritiesOptions{}) +} + +// ListPrioritiesWithContext lists existing priorities. +func (c *Client) ListPrioritiesWithContext(ctx context.Context, o ListPrioritiesOptions) (*ListPrioritiesResponse, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + + resp, err := c.get(ctx, "/priorities?"+v.Encode(), nil) + if err != nil { + return nil, err } - var p Priorities - e = json.NewDecoder(resp.Body).Decode(&p) - if e != nil { - return nil, e + var p ListPrioritiesResponse + if err := c.decodeJSON(resp, &p); err != nil { + return nil, err } return &p, nil diff --git a/vendor/github.com/PagerDuty/go-pagerduty/response_play.go b/vendor/github.com/PagerDuty/go-pagerduty/response_play.go new file mode 100644 index 0000000..a667f53 --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/response_play.go @@ -0,0 +1,148 @@ +package pagerduty + +import ( + "context" + "fmt" + "net/http" + + "github.com/google/go-querystring/query" +) + +// ResponsePlay represents the API object for a response object: +// +// https://developer.pagerduty.com/api-reference/b3A6Mjc0ODE2Ng-create-a-response-play +type ResponsePlay struct { + ID string `json:"id,omitempty"` + Type string `json:"type,omitempty"` + Summary string `json:"summary,omitempty"` + Self string `json:"self,omitempty"` + HTMLURL string `json:"html_url,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description"` + Team *APIReference `json:"team,omitempty"` + Subscribers []*APIReference `json:"subscribers,omitempty"` + SubscribersMessage string `json:"subscribers_message"` + Responders []*APIReference `json:"responders,omitempty"` + RespondersMessage string `json:"responders_message"` + Runnability *string `json:"runnability"` + ConferenceNumber *string `json:"conference_number"` + ConferenceURL *string `json:"conference_url"` + ConferenceType *string `json:"conference_type"` +} + +// ListResponsePlaysResponse represents the list of response plays. +type ListResponsePlaysResponse struct { + ResponsePlays []ResponsePlay `json:"response_plays"` +} + +// ListResponsePlaysOptions are the options for listing response plays. +type ListResponsePlaysOptions struct { + // FilterForManualRun limits results to show only response plays that can be + // invoked manually. + FilterForManualRun bool `url:"filter_for_manual_run,omitempty"` + + Query string `url:"query,omitempty"` + + From string +} + +// ListResponsePlays lists existing response plays. +func (c *Client) ListResponsePlays(ctx context.Context, o ListResponsePlaysOptions) ([]ResponsePlay, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + + h := map[string]string{ + "From": o.From, + } + + resp, err := c.get(ctx, "/response_plays?"+v.Encode(), h) + if err != nil { + return nil, err + } + + var result ListResponsePlaysResponse + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return result.ResponsePlays, nil +} + +// CreateResponsePlay creates a new response play. +func (c *Client) CreateResponsePlay(ctx context.Context, rp ResponsePlay) (ResponsePlay, error) { + d := map[string]ResponsePlay{ + "response_play": rp, + } + + resp, err := c.post(ctx, "/response_plays", d, nil) + return getResponsePlayFromResponse(c, resp, err) +} + +// GetResponsePlay gets details about an existing response play. +func (c *Client) GetResponsePlay(ctx context.Context, id string) (ResponsePlay, error) { + resp, err := c.get(ctx, "/response_plays/"+id, nil) + return getResponsePlayFromResponse(c, resp, err) +} + +// UpdateResponsePlay updates an existing response play. +func (c *Client) UpdateResponsePlay(ctx context.Context, rp ResponsePlay) (ResponsePlay, error) { + d := map[string]ResponsePlay{ + "response_play": rp, + } + + resp, err := c.put(ctx, "/response_plays/"+rp.ID, d, nil) + return getResponsePlayFromResponse(c, resp, err) +} + +// DeleteResponsePlay deletes an existing response play. +func (c *Client) DeleteResponsePlay(ctx context.Context, id string) error { + _, err := c.delete(ctx, "/response_plays/"+id) + return err +} + +// RunResponsePlay runs a response play on a given incident. +func (c *Client) RunResponsePlay(ctx context.Context, from string, responsePlayID string, incidentID string) error { + d := map[string]APIReference{ + "incident": { + ID: incidentID, + Type: "incident_reference", + }, + } + + h := map[string]string{ + "From": from, + } + + resp, err := c.post(ctx, "/response_plays/"+responsePlayID+"/run", d, h) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("failed to run response play %s on incident %s (status code: %d)", responsePlayID, incidentID, resp.StatusCode) + } + + return nil +} + +func getResponsePlayFromResponse(c *Client, resp *http.Response, err error) (ResponsePlay, error) { + if err != nil { + return ResponsePlay{}, err + } + + var target map[string]ResponsePlay + if dErr := c.decodeJSON(resp, &target); dErr != nil { + return ResponsePlay{}, fmt.Errorf("Could not decode JSON response: %v", dErr) + } + + const rootNode = "response_play" + + t, nodeOK := target[rootNode] + if !nodeOK { + return ResponsePlay{}, fmt.Errorf("JSON response does not have %s field", rootNode) + } + + return t, nil +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/ruleset.go b/vendor/github.com/PagerDuty/go-pagerduty/ruleset.go new file mode 100644 index 0000000..241f339 --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/ruleset.go @@ -0,0 +1,407 @@ +package pagerduty + +import ( + "context" + "fmt" + "net/http" +) + +// Ruleset represents a ruleset. +type Ruleset struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` + Self string `json:"self,omitempty"` + RoutingKeys []string `json:"routing_keys,omitempty"` + CreatedAt string `json:"created_at"` + Creator *RulesetObject `json:"creator,omitempty"` + UpdatedAt string `json:"updated_at"` + Updater *RulesetObject `json:"updater,omitempty"` + Team *RulesetObject `json:"team,omitempty"` +} + +// RulesetObject represents a generic object that is common within a ruleset object +type RulesetObject struct { + ID string `json:"id,omitempty"` + Type string `json:"type,omitempty"` + Self string `json:"self,omitempty"` +} + +// RulesetPayload represents payload with a ruleset object +type RulesetPayload struct { + Ruleset *Ruleset `json:"ruleset,omitempty"` +} + +// ListRulesetsResponse represents a list response of rulesets. +type ListRulesetsResponse struct { + Total uint `json:"total,omitempty"` + Rulesets []*Ruleset `json:"rulesets,omitempty"` + Offset uint `json:"offset,omitempty"` + More bool `json:"more,omitempty"` + Limit uint `json:"limit,omitempty"` +} + +// RulesetRule represents a Ruleset rule +type RulesetRule struct { + ID string `json:"id,omitempty"` + Self string `json:"self,omitempty"` + Position *int `json:"position,omitempty"` + Disabled bool `json:"disabled,omitempty"` + Conditions *RuleConditions `json:"conditions,omitempty"` + Actions *RuleActions `json:"actions,omitempty"` + Ruleset *APIObject `json:"ruleset,omitempty"` + CatchAll bool `json:"catch_all,omitempty"` + TimeFrame *RuleTimeFrame `json:"time_frame,omitempty"` +} + +// RulesetRulePayload represents a payload for ruleset rules +type RulesetRulePayload struct { + Rule *RulesetRule `json:"rule,omitempty"` +} + +// RuleConditions represents the conditions field for a Ruleset +type RuleConditions struct { + Operator string `json:"operator,omitempty"` + RuleSubconditions []*RuleSubcondition `json:"subconditions,omitempty"` +} + +// RuleSubcondition represents a subcondition of a ruleset condition +type RuleSubcondition struct { + Operator string `json:"operator,omitempty"` + Parameters *ConditionParameter `json:"parameters,omitempty"` +} + +// ConditionParameter represents parameters in a rule condition +type ConditionParameter struct { + Path string `json:"path,omitempty"` + Value string `json:"value,omitempty"` +} + +// RuleTimeFrame represents a time_frame object on the rule object +type RuleTimeFrame struct { + ScheduledWeekly *ScheduledWeekly `json:"scheduled_weekly,omitempty"` + ActiveBetween *ActiveBetween `json:"active_between,omitempty"` +} + +// ScheduledWeekly represents a time_frame object for scheduling rules weekly +type ScheduledWeekly struct { + // Weekdays is a 0 indexed slice of days, where 0 is Sunday and 6 is + // Saturday, when the window is scheduled for. + Weekdays []int `json:"weekdays,omitempty"` + + Timezone string `json:"timezone,omitempty"` + + // StartTime is the number of milliseconds into the day at which the window + // starts. + StartTime int `json:"start_time,omitempty"` + + // Duration is the window duration in milliseconds. + Duration int `json:"duration,omitempty"` +} + +// ActiveBetween represents an active_between object for setting a timeline for rules +type ActiveBetween struct { + // StartTime is in the number of milliseconds into the day at which the + // window starts. + StartTime int `json:"start_time,omitempty"` + + // EndTime is the number of milliseconds into the day at which the window + // ends. + EndTime int `json:"end_time,omitempty"` +} + +// ListRulesetRulesResponse represents a list of rules in a ruleset +type ListRulesetRulesResponse struct { + Total uint `json:"total,omitempty"` + Rules []*RulesetRule `json:"rules,omitempty"` + Offset uint `json:"offset,omitempty"` + More bool `json:"more,omitempty"` + Limit uint `json:"limit,omitempty"` +} + +// RuleActions represents a rule action +type RuleActions struct { + Annotate *RuleActionParameter `json:"annotate,omitempty"` + EventAction *RuleActionParameter `json:"event_action,omitempty"` + Extractions []*RuleActionExtraction `json:"extractions,omitempty"` + Priority *RuleActionParameter `json:"priority,omitempty"` + Severity *RuleActionParameter `json:"severity,omitempty"` + Suppress *RuleActionSuppress `json:"suppress,omitempty"` + Suspend *RuleActionSuspend `json:"suspend,omitempty"` + Route *RuleActionParameter `json:"route"` +} + +// RuleActionParameter represents a generic parameter object on a rule action +type RuleActionParameter struct { + Value string `json:"value,omitempty"` +} + +// RuleActionSuppress represents a rule suppress action object +type RuleActionSuppress struct { + Value bool `json:"value"` + ThresholdValue int `json:"threshold_value,omitempty"` + ThresholdTimeUnit string `json:"threshold_time_unit,omitempty"` + ThresholdTimeAmount int `json:"threshold_time_amount,omitempty"` +} + +// RuleActionSuspend represents a rule suspend action object +type RuleActionSuspend struct { + // Value specifies for how long to suspend the alert in seconds. + Value int `json:"value,omitempty"` +} + +// RuleActionExtraction represents a rule extraction action object +type RuleActionExtraction struct { + Target string `json:"target,omitempty"` + Source string `json:"source,omitempty"` + Regex string `json:"regex,omitempty"` +} + +// ListRulesets gets all rulesets. This method currently handles pagination of +// the response, so all rulesets should be present. +// +// Please note that the automatic pagination will be removed in v2 of this +// package, so it's recommended to use ListRulesetsPaginated instead. +func (c *Client) ListRulesets() (*ListRulesetsResponse, error) { + rs, err := c.ListRulesetsPaginated(context.Background()) + if err != nil { + return nil, err + } + + return &ListRulesetsResponse{Rulesets: rs}, nil +} + +// ListRulesetsPaginated gets all rulesets. +func (c *Client) ListRulesetsPaginated(ctx context.Context) ([]*Ruleset, error) { + var rulesets []*Ruleset + + // Create a handler closure capable of parsing data from the rulesets endpoint + // and appending resultant rulesets to the return slice. + responseHandler := func(response *http.Response) (APIListObject, error) { + var result ListRulesetsResponse + if err := c.decodeJSON(response, &result); err != nil { + return APIListObject{}, err + } + + rulesets = append(rulesets, result.Rulesets...) + + // Return stats on the current page. Caller can use this information to + // adjust for requesting additional pages. + return APIListObject{ + More: result.More, + Offset: result.Offset, + Limit: result.Limit, + }, nil + } + + // Make call to get all pages associated with the base endpoint. + if err := c.pagedGet(ctx, "/rulesets/", responseHandler); err != nil { + return nil, err + } + + return rulesets, nil +} + +// CreateRuleset creates a new ruleset. +// +// Deprecated: Use CreateRulesetWithContext instead. +func (c *Client) CreateRuleset(r *Ruleset) (*Ruleset, error) { + return c.CreateRulesetWithContext(context.Background(), r) +} + +// CreateRulesetWithContext creates a new ruleset. +func (c *Client) CreateRulesetWithContext(ctx context.Context, r *Ruleset) (*Ruleset, error) { + d := map[string]*Ruleset{ + "ruleset": r, + } + + resp, err := c.post(ctx, "/rulesets", d, nil) + return getRulesetFromResponse(c, resp, err) +} + +// DeleteRuleset deletes a ruleset. +// +// Deprecated: Use DeleteRulesetWithContext instead. +func (c *Client) DeleteRuleset(id string) error { + return c.DeleteRulesetWithContext(context.Background(), id) +} + +// DeleteRulesetWithContext deletes a ruleset. +func (c *Client) DeleteRulesetWithContext(ctx context.Context, id string) error { + _, err := c.delete(ctx, "/rulesets/"+id) + return err +} + +// GetRuleset gets details about a ruleset. +// +// Deprecated: Use GetRulesetWithContext instead. +func (c *Client) GetRuleset(id string) (*Ruleset, error) { + return c.GetRulesetWithContext(context.Background(), id) +} + +// GetRulesetWithContext gets details about a ruleset. +func (c *Client) GetRulesetWithContext(ctx context.Context, id string) (*Ruleset, error) { + resp, err := c.get(ctx, "/rulesets/"+id, nil) + return getRulesetFromResponse(c, resp, err) +} + +// UpdateRuleset updates a ruleset. +// +// Deprecated: Use UpdateRulesetWithContext instead. +func (c *Client) UpdateRuleset(r *Ruleset) (*Ruleset, error) { + return c.UpdateRulesetWithContext(context.Background(), r) +} + +// UpdateRulesetWithContext updates a ruleset. +func (c *Client) UpdateRulesetWithContext(ctx context.Context, r *Ruleset) (*Ruleset, error) { + d := map[string]*Ruleset{ + "ruleset": r, + } + + resp, err := c.put(ctx, "/rulesets/"+r.ID, d, nil) + return getRulesetFromResponse(c, resp, err) +} + +func getRulesetFromResponse(c *Client, resp *http.Response, err error) (*Ruleset, error) { + if err != nil { + return nil, err + } + + var target map[string]Ruleset + if dErr := c.decodeJSON(resp, &target); dErr != nil { + return nil, fmt.Errorf("Could not decode JSON response: %v", dErr) + } + + t, nodeOK := target["ruleset"] + if !nodeOK { + return nil, fmt.Errorf("JSON response does not have ruleset field") + } + + return &t, nil +} + +// ListRulesetRules gets all rules for a ruleset. This method currently handles pagination of +// the response, so all RuleseRule should be present. +// +// Please note that the automatic pagination will be removed in v2 of this +// package, so it's recommended to use ListRulesetRulesPaginated instead. +func (c *Client) ListRulesetRules(rulesetID string) (*ListRulesetRulesResponse, error) { + rsr, err := c.ListRulesetRulesPaginated(context.Background(), rulesetID) + if err != nil { + return nil, err + } + + return &ListRulesetRulesResponse{Rules: rsr}, nil +} + +// ListRulesetRulesPaginated gets all rules for a ruleset. +func (c *Client) ListRulesetRulesPaginated(ctx context.Context, rulesetID string) ([]*RulesetRule, error) { + var rules []*RulesetRule + + // Create a handler closure capable of parsing data from the ruleset rules endpoint + // and appending resultant ruleset rules to the return slice. + responseHandler := func(response *http.Response) (APIListObject, error) { + var result ListRulesetRulesResponse + + if err := c.decodeJSON(response, &result); err != nil { + return APIListObject{}, err + } + + rules = append(rules, result.Rules...) + + // Return stats on the current page. Caller can use this information to + // adjust for requesting additional pages. + return APIListObject{ + More: result.More, + Offset: result.Offset, + Limit: result.Limit, + }, nil + } + + // Make call to get all pages associated with the base endpoint. + if err := c.pagedGet(ctx, "/rulesets/"+rulesetID+"/rules", responseHandler); err != nil { + return nil, err + } + + return rules, nil +} + +// GetRulesetRule gets an event rule. +// +// Deprecated: Use GetRulesetRuleWithContext instead. +func (c *Client) GetRulesetRule(rulesetID, ruleID string) (*RulesetRule, error) { + return c.GetRulesetRuleWithContext(context.Background(), rulesetID, ruleID) +} + +// GetRulesetRuleWithContext gets an event rule +func (c *Client) GetRulesetRuleWithContext(ctx context.Context, rulesetID, ruleID string) (*RulesetRule, error) { + resp, err := c.get(ctx, "/rulesets/"+rulesetID+"/rules/"+ruleID, nil) + return getRuleFromResponse(c, resp, err) +} + +// DeleteRulesetRule deletes a rule. +// +// Deprecated: Use DeleteRulesetRuleWithContext instead. +func (c *Client) DeleteRulesetRule(rulesetID, ruleID string) error { + return c.DeleteRulesetRuleWithContext(context.Background(), rulesetID, ruleID) +} + +// DeleteRulesetRuleWithContext deletes a rule. +func (c *Client) DeleteRulesetRuleWithContext(ctx context.Context, rulesetID, ruleID string) error { + _, err := c.delete(ctx, "/rulesets/"+rulesetID+"/rules/"+ruleID) + return err +} + +// CreateRulesetRule creates a new rule for a ruleset. +// +// Deprecated: Use CreateRulesetRuleWithContext instead. +func (c *Client) CreateRulesetRule(rulesetID string, rule *RulesetRule) (*RulesetRule, error) { + return c.CreateRulesetRuleWithContext(context.Background(), rulesetID, rule) +} + +// CreateRulesetRuleWithContext creates a new rule for a ruleset. +func (c *Client) CreateRulesetRuleWithContext(ctx context.Context, rulesetID string, rule *RulesetRule) (*RulesetRule, error) { + d := map[string]*RulesetRule{ + "rule": rule, + } + + resp, err := c.post(ctx, "/rulesets/"+rulesetID+"/rules/", d, nil) + return getRuleFromResponse(c, resp, err) +} + +// UpdateRulesetRule updates a rule. +// +// Deprecated: Use UpdateRulesetRuleWithContext instead. +func (c *Client) UpdateRulesetRule(rulesetID, ruleID string, r *RulesetRule) (*RulesetRule, error) { + return c.UpdateRulesetRuleWithContext(context.Background(), rulesetID, ruleID, r) +} + +// UpdateRulesetRuleWithContext updates a rule. +func (c *Client) UpdateRulesetRuleWithContext(ctx context.Context, rulesetID, ruleID string, r *RulesetRule) (*RulesetRule, error) { + d := map[string]*RulesetRule{ + "rule": r, + } + + resp, err := c.put(ctx, "/rulesets/"+rulesetID+"/rules/"+ruleID, d, nil) + return getRuleFromResponse(c, resp, err) +} + +func getRuleFromResponse(c *Client, resp *http.Response, err error) (*RulesetRule, error) { + if err != nil { + return nil, err + } + + var target map[string]RulesetRule + if dErr := c.decodeJSON(resp, &target); dErr != nil { + return nil, fmt.Errorf("Could not decode JSON response: %v", dErr) + } + + const rootNode = "rule" + + t, nodeOK := target[rootNode] + if !nodeOK { + return nil, fmt.Errorf("JSON response does not have %s field", rootNode) + } + + return &t, nil +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/schedule.go b/vendor/github.com/PagerDuty/go-pagerduty/schedule.go index ea40fc4..361e392 100644 --- a/vendor/github.com/PagerDuty/go-pagerduty/schedule.go +++ b/vendor/github.com/PagerDuty/go-pagerduty/schedule.go @@ -1,6 +1,7 @@ package pagerduty import ( + "context" "fmt" "net/http" @@ -44,6 +45,7 @@ type Schedule struct { Description string `json:"description,omitempty"` EscalationPolicies []APIObject `json:"escalation_policies,omitempty"` Users []APIObject `json:"users,omitempty"` + Teams []APIObject `json:"teams,omitempty"` ScheduleLayers []ScheduleLayer `json:"schedule_layers,omitempty"` OverrideSubschedule ScheduleLayer `json:"override_subschedule,omitempty"` FinalSchedule ScheduleLayer `json:"final_schedule,omitempty"` @@ -51,8 +53,27 @@ type Schedule struct { // ListSchedulesOptions is the data structure used when calling the ListSchedules API endpoint. type ListSchedulesOptions struct { - APIListObject - Query string `url:"query,omitempty"` + // Limit is the pagination parameter that limits the number of results per + // page. PagerDuty defaults this value to 25 if omitted, and sets an upper + // bound of 100. + Limit uint `url:"limit,omitempty"` + + // Offset is the pagination parameter that specifies the offset at which to + // start pagination results. When trying to request the next page of + // results, the new Offset value should be currentOffset + Limit. + Offset uint `url:"offset,omitempty"` + + // Total is the pagination parameter to request that the API return the + // total count of items in the response. If this field is omitted or set to + // false, the total number of results will not be sent back from the PagerDuty API. + // + // Setting this to true will slow down the API response times, and so it's + // recommended to omit it unless you've a specific reason for wanting the + // total count of items in the collection. + Total bool `url:"total,omitempty"` + + Query string `url:"query,omitempty"` + Includes []string `url:"include,omitempty,brackets"` } // ListSchedulesResponse is the data structure returned from calling the ListSchedules API endpoint. @@ -67,75 +88,120 @@ type UserReference struct { } // ListSchedules lists the on-call schedules. +// +// Deprecated: Use ListSchedulesWithContext instead. func (c *Client) ListSchedules(o ListSchedulesOptions) (*ListSchedulesResponse, error) { + return c.ListSchedulesWithContext(context.Background(), o) +} + +// ListSchedulesWithContext lists the on-call schedules. +func (c *Client) ListSchedulesWithContext(ctx context.Context, o ListSchedulesOptions) (*ListSchedulesResponse, error) { v, err := query.Values(o) if err != nil { return nil, err } - resp, err := c.get("/schedules?" + v.Encode()) + + resp, err := c.get(ctx, "/schedules?"+v.Encode(), nil) if err != nil { return nil, err } + var result ListSchedulesResponse - return &result, c.decodeJSON(resp, &result) + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil } // CreateSchedule creates a new on-call schedule. +// +// Deprecated: Use CreateScheduleWithContext instead. func (c *Client) CreateSchedule(s Schedule) (*Schedule, error) { - data := make(map[string]Schedule) - data["schedule"] = s - resp, err := c.post("/schedules", data, nil) - if err != nil { - return nil, err + return c.CreateScheduleWithContext(context.Background(), s) +} + +// CreateScheduleWithContext creates a new on-call schedule. +func (c *Client) CreateScheduleWithContext(ctx context.Context, s Schedule) (*Schedule, error) { + d := map[string]Schedule{ + "schedule": s, } - return getScheduleFromResponse(c, resp) + + resp, err := c.post(ctx, "/schedules", d, nil) + return getScheduleFromResponse(c, resp, err) } // PreviewScheduleOptions is the data structure used when calling the PreviewSchedule API endpoint. type PreviewScheduleOptions struct { - APIListObject Since string `url:"since,omitempty"` Until string `url:"until,omitempty"` Overflow bool `url:"overflow,omitempty"` } -// PreviewSchedule previews what an on-call schedule would look like without saving it. +// PreviewSchedule previews what an on-call schedule would look like without +// saving it. +// +// Deprecated: Use PreviewScheduleWithContext instead. func (c *Client) PreviewSchedule(s Schedule, o PreviewScheduleOptions) error { + return c.PreviewScheduleWithContext(context.Background(), s, o) +} + +// PreviewScheduleWithContext previews what an on-call schedule would look like +// without saving it. Nothing is returned from this method, because the API +// should return the Schedule as we posted it. If this method call returns no +// error, the schedule should be valid and can be updated. +func (c *Client) PreviewScheduleWithContext(ctx context.Context, s Schedule, o PreviewScheduleOptions) error { v, err := query.Values(o) if err != nil { return err } - var data map[string]Schedule - data["schedule"] = s - _, e := c.post("/schedules/preview?"+v.Encode(), data, nil) - return e + + d := map[string]Schedule{ + "schedule": s, + } + + _, err = c.post(ctx, "/schedules/preview?"+v.Encode(), d, nil) + return err } // DeleteSchedule deletes an on-call schedule. +// +// Deprecated: Use DeleteScheduleWithContext instead. func (c *Client) DeleteSchedule(id string) error { - _, err := c.delete("/schedules/" + id) + return c.DeleteScheduleWithContext(context.Background(), id) +} + +// DeleteScheduleWithContext deletes an on-call schedule. +func (c *Client) DeleteScheduleWithContext(ctx context.Context, id string) error { + _, err := c.delete(ctx, "/schedules/"+id) return err } // GetScheduleOptions is the data structure used when calling the GetSchedule API endpoint. type GetScheduleOptions struct { - APIListObject TimeZone string `url:"time_zone,omitempty"` Since string `url:"since,omitempty"` Until string `url:"until,omitempty"` } -// GetSchedule shows detailed information about a schedule, including entries for each layer and sub-schedule. +// GetSchedule shows detailed information about a schedule, including entries +// for each layer and sub-schedule. +// +// Deprecated: Use GetScheduleWithContext instead. func (c *Client) GetSchedule(id string, o GetScheduleOptions) (*Schedule, error) { + return c.GetScheduleWithContext(context.Background(), id, o) +} + +// GetScheduleWithContext shows detailed information about a schedule, including +// entries for each layer and sub-schedule. +func (c *Client) GetScheduleWithContext(ctx context.Context, id string, o GetScheduleOptions) (*Schedule, error) { v, err := query.Values(o) if err != nil { return nil, fmt.Errorf("Could not parse values for query: %v", err) } - resp, err := c.get("/schedules/" + id + "?" + v.Encode()) - if err != nil { - return nil, err - } - return getScheduleFromResponse(c, resp) + + resp, err := c.get(ctx, "/schedules/"+id+"?"+v.Encode(), nil) + return getScheduleFromResponse(c, resp, err) } // UpdateScheduleOptions is the data structure used when calling the UpdateSchedule API endpoint. @@ -144,19 +210,24 @@ type UpdateScheduleOptions struct { } // UpdateSchedule updates an existing on-call schedule. +// +// Deprecated: Use UpdateScheduleWithContext instead. func (c *Client) UpdateSchedule(id string, s Schedule) (*Schedule, error) { - v := make(map[string]Schedule) - v["schedule"] = s - resp, err := c.put("/schedules/"+id, v, nil) - if err != nil { - return nil, err + return c.UpdateScheduleWithContext(context.Background(), id, s) +} + +// UpdateScheduleWithContext updates an existing on-call schedule. +func (c *Client) UpdateScheduleWithContext(ctx context.Context, id string, s Schedule) (*Schedule, error) { + d := map[string]Schedule{ + "schedule": s, } - return getScheduleFromResponse(c, resp) + + resp, err := c.put(ctx, "/schedules/"+id, d, nil) + return getScheduleFromResponse(c, resp, err) } // ListOverridesOptions is the data structure used when calling the ListOverrides API endpoint. type ListOverridesOptions struct { - APIListObject Since string `url:"since,omitempty"` Until string `url:"until,omitempty"` Editable bool `url:"editable,omitempty"` @@ -165,87 +236,141 @@ type ListOverridesOptions struct { // ListOverridesResponse is the data structure returned from calling the ListOverrides API endpoint. type ListOverridesResponse struct { - APIListObject Overrides []Override `json:"overrides,omitempty"` } -// Overrides are any schedule layers from the override layer. +// Override are any schedule layers from the override layer. type Override struct { - ID string `json:"id,omitempty"` - Start string `json:"start,omitempty"` - End string `json:"end,omitempty"` - User APIObject `json:"user,omitempty"` + ID string `json:"id,omitempty"` + Type string `json:"type,omitempty"` + Summary string `json:"summary,omitempty"` + Self string `json:"self,omitempty"` + HTMLURL string `json:"html_url,omitempty"` + Start string `json:"start,omitempty"` + End string `json:"end,omitempty"` + User APIObject `json:"user,omitempty"` } // ListOverrides lists overrides for a given time range. +// +// Deprecated: Use ListOverridesWithContext instead. func (c *Client) ListOverrides(id string, o ListOverridesOptions) (*ListOverridesResponse, error) { + return c.ListOverridesWithContext(context.Background(), id, o) +} + +// ListOverridesWithContext lists overrides for a given time range. +func (c *Client) ListOverridesWithContext(ctx context.Context, id string, o ListOverridesOptions) (*ListOverridesResponse, error) { v, err := query.Values(o) if err != nil { return nil, err } - resp, err := c.get("/schedules/" + id + "/overrides?" + v.Encode()) + + resp, err := c.get(ctx, "/schedules/"+id+"/overrides?"+v.Encode(), nil) if err != nil { return nil, err } + var result ListOverridesResponse - return &result, c.decodeJSON(resp, &result) + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil } -// CreateOverride creates an override for a specific user covering the specified time range. +// CreateOverride creates an override for a specific user covering the specified +// time range. +// +// Deprecated: Use CreateOverrideWithContext instead. func (c *Client) CreateOverride(id string, o Override) (*Override, error) { - data := make(map[string]Override) - data["override"] = o - resp, err := c.post("/schedules/"+id+"/overrides", data, nil) + return c.CreateOverrideWithContext(context.Background(), id, o) +} + +// CreateOverrideWithContext creates an override for a specific user covering +// the specified time range. +func (c *Client) CreateOverrideWithContext(ctx context.Context, id string, o Override) (*Override, error) { + d := map[string]Override{ + "override": o, + } + + resp, err := c.post(ctx, "/schedules/"+id+"/overrides", d, nil) if err != nil { return nil, err } + return getOverrideFromResponse(c, resp) } // DeleteOverride removes an override. +// +// Deprecated: Use DeleteOverrideWithContext instead. func (c *Client) DeleteOverride(scheduleID, overrideID string) error { - _, err := c.delete("/schedules/" + scheduleID + "/overrides/" + overrideID) + return c.DeleteOverrideWithContext(context.Background(), scheduleID, overrideID) +} + +// DeleteOverrideWithContext removes an override. +func (c *Client) DeleteOverrideWithContext(ctx context.Context, scheduleID, overrideID string) error { + _, err := c.delete(ctx, "/schedules/"+scheduleID+"/overrides/"+overrideID) return err } // ListOnCallUsersOptions is the data structure used when calling the ListOnCallUsers API endpoint. type ListOnCallUsersOptions struct { - APIListObject Since string `url:"since,omitempty"` Until string `url:"until,omitempty"` } -// ListOnCallUsers lists all of the users on call in a given schedule for a given time range. +// ListOnCallUsers lists all of the users on call in a given schedule for a +// given time range. +// +// Deprecated: Use ListOnCallUsersWithContext instead. func (c *Client) ListOnCallUsers(id string, o ListOnCallUsersOptions) ([]User, error) { + return c.ListOnCallUsersWithContext(context.Background(), id, o) +} + +// ListOnCallUsersWithContext lists all of the users on call in a given schedule +// for a given time range. +func (c *Client) ListOnCallUsersWithContext(ctx context.Context, id string, o ListOnCallUsersOptions) ([]User, error) { v, err := query.Values(o) if err != nil { return nil, err } - resp, err := c.get("/schedules/" + id + "/users?" + v.Encode()) + + resp, err := c.get(ctx, "/schedules/"+id+"/users?"+v.Encode(), nil) if err != nil { return nil, err } + var result map[string][]User if err := c.decodeJSON(resp, &result); err != nil { return nil, err } + u, ok := result["users"] if !ok { return nil, fmt.Errorf("JSON response does not have users field") } + return u, nil } -func getScheduleFromResponse(c *Client, resp *http.Response) (*Schedule, error) { +func getScheduleFromResponse(c *Client, resp *http.Response, err error) (*Schedule, error) { + if err != nil { + return nil, err + } + var target map[string]Schedule if dErr := c.decodeJSON(resp, &target); dErr != nil { return nil, fmt.Errorf("Could not decode JSON response: %v", dErr) } - rootNode := "schedule" + + const rootNode = "schedule" + t, nodeOK := target[rootNode] if !nodeOK { return nil, fmt.Errorf("JSON response does not have %s field", rootNode) } + return &t, nil } @@ -254,10 +379,12 @@ func getOverrideFromResponse(c *Client, resp *http.Response) (*Override, error) if dErr := c.decodeJSON(resp, &target); dErr != nil { return nil, fmt.Errorf("Could not decode JSON response: %v", dErr) } - rootNode := "override" + + const rootNode = "override" o, nodeOK := target[rootNode] if !nodeOK { return nil, fmt.Errorf("JSON response does not have %s field", rootNode) } + return &o, nil } diff --git a/vendor/github.com/PagerDuty/go-pagerduty/service.go b/vendor/github.com/PagerDuty/go-pagerduty/service.go index 675077b..497b4c1 100644 --- a/vendor/github.com/PagerDuty/go-pagerduty/service.go +++ b/vendor/github.com/PagerDuty/go-pagerduty/service.go @@ -1,24 +1,13 @@ package pagerduty import ( + "context" "fmt" "net/http" "github.com/google/go-querystring/query" ) -// Integration is an endpoint (like Nagios, email, or an API call) that generates events, which are normalized and de-duplicated by PagerDuty to create incidents. -type Integration struct { - APIObject - Name string `json:"name,omitempty"` - Service *APIObject `json:"service,omitempty"` - CreatedAt string `json:"created_at,omitempty"` - Vendor *APIObject `json:"vendor,omitempty"` - Type string `json:"type,omitempty"` - IntegrationKey string `json:"integration_key,omitempty"` - IntegrationEmail string `json:"integration_email,omitempty"` -} - // InlineModel represents when a scheduled action will occur. type InlineModel struct { Type string `json:"type,omitempty"` @@ -55,30 +44,102 @@ type IncidentUrgencyRule struct { OutsideSupportHours *IncidentUrgencyType `json:"outside_support_hours,omitempty"` } +// ListServiceRulesResponse represents a list of rules in a service +type ListServiceRulesResponse struct { + Offset uint `json:"offset,omitempty"` + Limit uint `json:"limit,omitempty"` + More bool `json:"more,omitempty"` + Total uint `json:"total,omitempty"` + Rules []ServiceRule `json:"rules,omitempty"` +} + +// ServiceRule represents a Service rule +type ServiceRule struct { + ID string `json:"id,omitempty"` + Self string `json:"self,omitempty"` + Disabled *bool `json:"disabled,omitempty"` + Conditions *RuleConditions `json:"conditions,omitempty"` + TimeFrame *RuleTimeFrame `json:"time_frame,omitempty"` + Position *int `json:"position,omitempty"` + Actions *ServiceRuleActions `json:"actions,omitempty"` +} + +// ServiceRuleActions represents a rule action +type ServiceRuleActions struct { + Annotate *RuleActionParameter `json:"annotate,omitempty"` + EventAction *RuleActionParameter `json:"event_action,omitempty"` + Extractions []RuleActionExtraction `json:"extractions,omitempty"` + Priority *RuleActionParameter `json:"priority,omitempty"` + Severity *RuleActionParameter `json:"severity,omitempty"` + Suppress *RuleActionSuppress `json:"suppress,omitempty"` + Suspend *RuleActionSuspend `json:"suspend,omitempty"` +} + // Service represents something you monitor (like a web service, email service, or database service). type Service struct { APIObject - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - AutoResolveTimeout *uint `json:"auto_resolve_timeout"` - AcknowledgementTimeout *uint `json:"acknowledgement_timeout"` - CreateAt string `json:"created_at,omitempty"` - Status string `json:"status,omitempty"` - LastIncidentTimestamp string `json:"last_incident_timestamp,omitempty"` - Integrations []Integration `json:"integrations,omitempty"` - EscalationPolicy EscalationPolicy `json:"escalation_policy,omitempty"` - Teams []Team `json:"teams,omitempty"` - IncidentUrgencyRule *IncidentUrgencyRule `json:"incident_urgency_rule,omitempty"` - SupportHours *SupportHours `json:"support_hours,omitempty"` - ScheduledActions []ScheduledAction `json:"scheduled_actions,omitempty"` - AlertCreation string `json:"alert_creation,omitempty"` - AlertGrouping string `json:"alert_grouping,omitempty"` - AlertGroupingTimeout *uint `json:"alert_grouping_timeout,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + AutoResolveTimeout *uint `json:"auto_resolve_timeout,omitempty"` + AcknowledgementTimeout *uint `json:"acknowledgement_timeout,omitempty"` + CreateAt string `json:"created_at,omitempty"` + Status string `json:"status,omitempty"` + LastIncidentTimestamp string `json:"last_incident_timestamp,omitempty"` + Integrations []Integration `json:"integrations,omitempty"` + EscalationPolicy EscalationPolicy `json:"escalation_policy,omitempty"` + Teams []Team `json:"teams,omitempty"` + IncidentUrgencyRule *IncidentUrgencyRule `json:"incident_urgency_rule,omitempty"` + SupportHours *SupportHours `json:"support_hours,omitempty"` + ScheduledActions []ScheduledAction `json:"scheduled_actions,omitempty"` + AlertCreation string `json:"alert_creation,omitempty"` + AlertGrouping string `json:"alert_grouping,omitempty"` + AlertGroupingTimeout *uint `json:"alert_grouping_timeout,omitempty"` + AlertGroupingParameters *AlertGroupingParameters `json:"alert_grouping_parameters,omitempty"` + ResponsePlay *APIObject `json:"response_play,omitempty"` + Addons []Addon `json:"addons,omitempty"` + AutoPauseNotificationsParameters *AutoPauseNotificationsParameters `json:"auto_pause_notifications_parameters,omitempty"` +} + +// AutoPauseNotificationsParameters defines how alerts on the service will be automatically paused +type AutoPauseNotificationsParameters struct { + Enabled bool `json:"enabled"` + Timeout uint `json:"timeout,omitempty"` +} + +// AlertGroupingParameters defines how alerts on the service will be automatically grouped into incidents +type AlertGroupingParameters struct { + Type string `json:"type,omitempty"` + Config *AlertGroupParamsConfig `json:"config,omitempty"` +} + +// AlertGroupParamsConfig is the config object on alert_grouping_parameters +type AlertGroupParamsConfig struct { + Timeout *uint `json:"timeout,omitempty"` + Aggregate string `json:"aggregate,omitempty"` + Fields []string `json:"fields,omitempty"` } // ListServiceOptions is the data structure used when calling the ListServices API endpoint. type ListServiceOptions struct { - APIListObject + // Limit is the pagination parameter that limits the number of results per + // page. PagerDuty defaults this value to 25 if omitted, and sets an upper + // bound of 100. + Limit uint `url:"limit,omitempty"` + + // Offset is the pagination parameter that specifies the offset at which to + // start pagination results. When trying to request the next page of + // results, the new Offset value should be currentOffset + Limit. + Offset uint `url:"offset,omitempty"` + + // Total is the pagination parameter to request that the API return the + // total count of items in the response. If this field is omitted or set to + // false, the total number of results will not be sent back from the PagerDuty API. + // + // Setting this to true will slow down the API response times, and so it's + // recommended to omit it unless you've a specific reason for wanting the + // total count of items in the collection. + Total bool `url:"total,omitempty"` + TeamIDs []string `url:"team_ids,omitempty,brackets"` TimeZone string `url:"time_zone,omitempty"` SortBy string `url:"sort_by,omitempty"` @@ -93,17 +154,61 @@ type ListServiceResponse struct { } // ListServices lists existing services. +// +// Deprecated: Use ListServicesWithContext instead. func (c *Client) ListServices(o ListServiceOptions) (*ListServiceResponse, error) { + return c.ListServicesWithContext(context.Background(), o) +} + +// ListServicesWithContext lists existing services. +func (c *Client) ListServicesWithContext(ctx context.Context, o ListServiceOptions) (*ListServiceResponse, error) { v, err := query.Values(o) if err != nil { return nil, err } - resp, err := c.get("/services?" + v.Encode()) + + resp, err := c.get(ctx, "/services?"+v.Encode(), nil) if err != nil { return nil, err } + var result ListServiceResponse - return &result, c.decodeJSON(resp, &result) + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil +} + +// ListServicesPaginated lists existing services processing paginated responses +func (c *Client) ListServicesPaginated(ctx context.Context, o ListServiceOptions) ([]Service, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + + var services []Service + + responseHandler := func(response *http.Response) (APIListObject, error) { + var result ListServiceResponse + if err := c.decodeJSON(response, &result); err != nil { + return APIListObject{}, err + } + + services = append(services, result.Services...) + + return APIListObject{ + More: result.More, + Offset: result.Offset, + Limit: result.Limit, + }, nil + } + + if err := c.pagedGet(ctx, "/services?"+v.Encode(), responseHandler); err != nil { + return nil, err + } + + return services, nil } // GetServiceOptions is the data structure used when calling the GetService API endpoint. @@ -112,80 +217,169 @@ type GetServiceOptions struct { } // GetService gets details about an existing service. +// +// Deprecated: Use GetServiceWithContext instead. func (c *Client) GetService(id string, o *GetServiceOptions) (*Service, error) { + return c.GetServiceWithContext(context.Background(), id, o) +} + +// GetServiceWithContext gets details about an existing service. +func (c *Client) GetServiceWithContext(ctx context.Context, id string, o *GetServiceOptions) (*Service, error) { v, err := query.Values(o) - resp, err := c.get("/services/" + id + "?" + v.Encode()) + if err != nil { + return nil, err + } + + resp, err := c.get(ctx, "/services/"+id+"?"+v.Encode(), nil) return getServiceFromResponse(c, resp, err) } // CreateService creates a new service. +// +// Deprecated: Use CreateServiceWithContext instead. func (c *Client) CreateService(s Service) (*Service, error) { - data := make(map[string]Service) - data["service"] = s - resp, err := c.post("/services", data, nil) + return c.CreateServiceWithContext(context.Background(), s) +} + +// CreateServiceWithContext creates a new service. +func (c *Client) CreateServiceWithContext(ctx context.Context, s Service) (*Service, error) { + d := map[string]Service{ + "service": s, + } + + resp, err := c.post(ctx, "/services", d, nil) return getServiceFromResponse(c, resp, err) } // UpdateService updates an existing service. +// +// Deprecated: Use UpdateServiceWithContext instead. func (c *Client) UpdateService(s Service) (*Service, error) { - resp, err := c.put("/services/"+s.ID, s, nil) + return c.UpdateServiceWithContext(context.Background(), s) +} + +// UpdateServiceWithContext updates an existing service. +func (c *Client) UpdateServiceWithContext(ctx context.Context, s Service) (*Service, error) { + d := map[string]Service{ + "service": s, + } + + resp, err := c.put(ctx, "/services/"+s.ID, d, nil) return getServiceFromResponse(c, resp, err) } // DeleteService deletes an existing service. +// +// Deprecated: Use DeleteServiceWithContext instead. func (c *Client) DeleteService(id string) error { - _, err := c.delete("/services/" + id) + return c.DeleteServiceWithContext(context.Background(), id) +} + +// DeleteServiceWithContext deletes an existing service. +func (c *Client) DeleteServiceWithContext(ctx context.Context, id string) error { + _, err := c.delete(ctx, "/services/"+id) return err } -// CreateIntegration creates a new integration belonging to a service. -func (c *Client) CreateIntegration(id string, i Integration) (*Integration, error) { - data := make(map[string]Integration) - data["integration"] = i - resp, err := c.post("/services/"+id+"/integrations", data, nil) - return getIntegrationFromResponse(c, resp, err) +// ListServiceRulesPaginated gets all rules for a service. +func (c *Client) ListServiceRulesPaginated(ctx context.Context, serviceID string) ([]ServiceRule, error) { + var rules []ServiceRule + + // Create a handler closure capable of parsing data from the Service rules endpoint + // and appending resultant Service rules to the return slice. + responseHandler := func(response *http.Response) (APIListObject, error) { + var result ListServiceRulesResponse + + if err := c.decodeJSON(response, &result); err != nil { + return APIListObject{}, err + } + + rules = append(rules, result.Rules...) + + // Return stats on the current page. Caller can use this information to + // adjust for requesting additional pages. + return APIListObject{ + More: result.More, + Offset: result.Offset, + Limit: result.Limit, + }, nil + } + + // Make call to get all pages associated with the base endpoint. + if err := c.pagedGet(ctx, "/services/"+serviceID+"/rules", responseHandler); err != nil { + return nil, err + } + + return rules, nil } -// GetIntegrationOptions is the data structure used when calling the GetIntegration API endpoint. -type GetIntegrationOptions struct { - Includes []string `url:"include,omitempty,brackets"` +// GetServiceRule gets a service rule. +func (c *Client) GetServiceRule(ctx context.Context, serviceID, ruleID string) (ServiceRule, error) { + resp, err := c.get(ctx, "/services/"+serviceID+"/rules/"+ruleID, nil) + return getServiceRuleFromResponse(c, resp, err) } -// GetIntegration gets details about an integration belonging to a service. -func (c *Client) GetIntegration(serviceID, integrationID string, o GetIntegrationOptions) (*Integration, error) { - v, queryErr := query.Values(o) - if queryErr != nil { - return nil, queryErr +// DeleteServiceRule deletes a service rule. +func (c *Client) DeleteServiceRule(ctx context.Context, serviceID, ruleID string) error { + _, err := c.delete(ctx, "/services/"+serviceID+"/rules/"+ruleID) + return err +} + +// CreateServiceRule creates a service rule. +func (c *Client) CreateServiceRule(ctx context.Context, serviceID string, rule ServiceRule) (ServiceRule, error) { + d := map[string]ServiceRule{ + "rule": rule, } - resp, err := c.get("/services/" + serviceID + "/integrations/" + integrationID + "?" + v.Encode()) - return getIntegrationFromResponse(c, resp, err) + resp, err := c.post(ctx, "/services/"+serviceID+"/rules/", d, nil) + return getServiceRuleFromResponse(c, resp, err) } -// UpdateIntegration updates an integration belonging to a service. -func (c *Client) UpdateIntegration(serviceID string, i Integration) (*Integration, error) { - resp, err := c.put("/services/"+serviceID+"/integrations/"+i.ID, i, nil) - return getIntegrationFromResponse(c, resp, err) +// UpdateServiceRule updates a service rule. +func (c *Client) UpdateServiceRule(ctx context.Context, serviceID, ruleID string, rule ServiceRule) (ServiceRule, error) { + d := map[string]ServiceRule{ + "rule": rule, + } + resp, err := c.put(ctx, "/services/"+serviceID+"/rules/"+ruleID, d, nil) + return getServiceRuleFromResponse(c, resp, err) } -// DeleteIntegration deletes an existing integration. -func (c *Client) DeleteIntegration(serviceID string, integrationID string) error { - _, err := c.delete("/services/" + serviceID + "/integrations/" + integrationID) - return err +func getServiceRuleFromResponse(c *Client, resp *http.Response, err error) (ServiceRule, error) { + if err != nil { + return ServiceRule{}, err + } + + var target map[string]ServiceRule + if dErr := c.decodeJSON(resp, &target); dErr != nil { + return ServiceRule{}, fmt.Errorf("Could not decode JSON response: %v", dErr) + } + + const rootNode = "rule" + + t, nodeOK := target[rootNode] + if !nodeOK { + return ServiceRule{}, fmt.Errorf("JSON response does not have %s field", rootNode) + } + + return t, nil } func getServiceFromResponse(c *Client, resp *http.Response, err error) (*Service, error) { if err != nil { return nil, err } + var target map[string]Service if dErr := c.decodeJSON(resp, &target); dErr != nil { return nil, fmt.Errorf("Could not decode JSON response: %v", dErr) } - rootNode := "service" + + const rootNode = "service" + t, nodeOK := target[rootNode] if !nodeOK { return nil, fmt.Errorf("JSON response does not have %s field", rootNode) } + return &t, nil } @@ -193,14 +387,18 @@ func getIntegrationFromResponse(c *Client, resp *http.Response, err error) (*Int if err != nil { return nil, err } + var target map[string]Integration if dErr := c.decodeJSON(resp, &target); dErr != nil { return nil, fmt.Errorf("Could not decode JSON response: %v", err) } - rootNode := "integration" + + const rootNode = "integration" + t, nodeOK := target[rootNode] if !nodeOK { return nil, fmt.Errorf("JSON response does not have %s field", rootNode) } + return &t, nil } diff --git a/vendor/github.com/PagerDuty/go-pagerduty/service_dependency.go b/vendor/github.com/PagerDuty/go-pagerduty/service_dependency.go new file mode 100644 index 0000000..b7017ae --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/service_dependency.go @@ -0,0 +1,112 @@ +package pagerduty + +import ( + "context" +) + +// ServiceDependency represents a relationship between a business and technical service +type ServiceDependency struct { + ID string `json:"id,omitempty"` + Type string `json:"type,omitempty"` + SupportingService *ServiceObj `json:"supporting_service,omitempty"` + DependentService *ServiceObj `json:"dependent_service,omitempty"` +} + +// ServiceObj represents a service object in service relationship +type ServiceObj struct { + ID string `json:"id,omitempty"` + Type string `json:"type,omitempty"` +} + +// ListServiceDependencies represents a list of dependencies for a service +type ListServiceDependencies struct { + Relationships []*ServiceDependency `json:"relationships,omitempty"` +} + +// ListBusinessServiceDependencies lists dependencies of a business service. +// +// Deprecated: Use ListBusinessServiceDependenciesWithContext instead. +func (c *Client) ListBusinessServiceDependencies(businessServiceID string) (*ListServiceDependencies, error) { + return c.ListBusinessServiceDependenciesWithContext(context.Background(), businessServiceID) +} + +// ListBusinessServiceDependenciesWithContext lists dependencies of a business service. +func (c *Client) ListBusinessServiceDependenciesWithContext(ctx context.Context, businessServiceID string) (*ListServiceDependencies, error) { + resp, err := c.get(ctx, "/service_dependencies/business_services/"+businessServiceID, nil) + if err != nil { + return nil, err + } + + var result ListServiceDependencies + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil +} + +// ListTechnicalServiceDependencies lists dependencies of a technical service. +// +// Deprecated: Use ListTechnicalServiceDependenciesWithContext instead. +func (c *Client) ListTechnicalServiceDependencies(serviceID string) (*ListServiceDependencies, error) { + return c.ListTechnicalServiceDependenciesWithContext(context.Background(), serviceID) +} + +// ListTechnicalServiceDependenciesWithContext lists dependencies of a technical service. +func (c *Client) ListTechnicalServiceDependenciesWithContext(ctx context.Context, serviceID string) (*ListServiceDependencies, error) { + resp, err := c.get(ctx, "/service_dependencies/technical_services/"+serviceID, nil) + if err != nil { + return nil, err + } + + var result ListServiceDependencies + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil +} + +// AssociateServiceDependencies Create new dependencies between two services. +// +// Deprecated: Use AssociateServiceDependenciesWithContext instead. +func (c *Client) AssociateServiceDependencies(dependencies *ListServiceDependencies) (*ListServiceDependencies, error) { + return c.AssociateServiceDependenciesWithContext(context.Background(), dependencies) +} + +// AssociateServiceDependenciesWithContext Create new dependencies between two services. +func (c *Client) AssociateServiceDependenciesWithContext(ctx context.Context, dependencies *ListServiceDependencies) (*ListServiceDependencies, error) { + resp, err := c.post(ctx, "/service_dependencies/associate", dependencies, nil) + if err != nil { + return nil, err + } + + var result ListServiceDependencies + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil +} + +// DisassociateServiceDependencies Disassociate dependencies between two services. +// +// Deprecated: Use DisassociateServiceDependenciesWithContext instead. +func (c *Client) DisassociateServiceDependencies(dependencies *ListServiceDependencies) (*ListServiceDependencies, error) { + return c.DisassociateServiceDependenciesWithContext(context.Background(), dependencies) +} + +// DisassociateServiceDependenciesWithContext Disassociate dependencies between two services. +func (c *Client) DisassociateServiceDependenciesWithContext(ctx context.Context, dependencies *ListServiceDependencies) (*ListServiceDependencies, error) { + resp, err := c.post(ctx, "/service_dependencies/disassociate", dependencies, nil) + if err != nil { + return nil, err + } + + var result ListServiceDependencies + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/service_integration.go b/vendor/github.com/PagerDuty/go-pagerduty/service_integration.go new file mode 100644 index 0000000..4ca60af --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/service_integration.go @@ -0,0 +1,352 @@ +package pagerduty + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + + "github.com/google/go-querystring/query" +) + +// IntegrationEmailFilterMode is a type to respresent the different filter modes +// for a Generic Email Integration. This defines how the email filter rules +// (IntegrationEmailFilterRuleMode) are used when emails are ingested. +type IntegrationEmailFilterMode uint8 + +const ( + // EmailFilterModeInvalid only exists to make it harder to use values of + // this type incorrectly. Please instead use one of EmailFilterModeAll, + // EmailFilterModeOr, EmailFilterModeAnd + // + // This value should not get marshaled to JSON by the encoding/json package. + EmailFilterModeInvalid IntegrationEmailFilterMode = iota + + // EmailFilterModeAll means that all incoming email will be be accepted, and + // no email rules will be considered. + EmailFilterModeAll + + // EmailFilterModeOr instructs the email filtering system to accept the + // email if one or more rules match the message. + EmailFilterModeOr + + // EmailFilterModeAnd instructs the email filtering system to accept the + // email only if all of the rules match the message. + EmailFilterModeAnd +) + +// string values for each IntegrationEmailFilterMode value +const ( + efmAll = "all-email" // EmailFilterModeAll + efmOr = "or-rules-email" // EmailFilterModeOr + efmAnd = "and-rules-email" // EmailFilterModeAnd +) + +func (i IntegrationEmailFilterMode) String() string { + switch i { + case EmailFilterModeAll: + return efmAll + + case EmailFilterModeOr: + return efmOr + + case EmailFilterModeAnd: + return efmAnd + + default: + return "invalid" + } +} + +// compile time encoding/json interface satisfaction assertions +var ( + _ json.Marshaler = IntegrationEmailFilterMode(0) + _ json.Unmarshaler = (*IntegrationEmailFilterMode)(nil) +) + +// MarshalJSON satisfies json.Marshaler +func (i IntegrationEmailFilterMode) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf("%q", i.String())), nil +} + +// UnmarshalJSON satisfies json.Unmarshaler +func (i *IntegrationEmailFilterMode) UnmarshalJSON(b []byte) error { + if b[0] != '"' { + if bytes.Equal(b, []byte(`null`)) { + return errors.New("value cannot be null") + } + + // just return json.Unmarshal error + var s string + + err := json.Unmarshal(b, &s) + if err == nil { + panic("this should not be possible...") + } + + return err + } + + v := string(b[1 : len(b)-1]) + + switch v { + case efmAll: + *i = EmailFilterModeAll + + case efmOr: + *i = EmailFilterModeOr + + case efmAnd: + *i = EmailFilterModeAnd + + default: + return fmt.Errorf("unknown value %q", v) + } + + return nil +} + +// IntegrationEmailFilterRuleMode is a type to represent the different matching +// modes of Generic Email Integration Filer Rules without consumers of this +// package needing to be intimately familiar with the specifics of the REST API. +type IntegrationEmailFilterRuleMode uint8 + +const ( + // EmailFilterRuleModeInvalid only exists to make it harder to use values of this + // type incorrectly. Please instead use one of EmailFilterRuleModeAlways, + // EmailFilterRuleModeMatch, or EmailFilterRuleModeNoMatch. + // + // This value should not get marshaled to JSON by the encoding/json package. + EmailFilterRuleModeInvalid IntegrationEmailFilterRuleMode = iota + + // EmailFilterRuleModeAlways means that the specific value can be anything. Any + // associated regular expression will be ignored. + EmailFilterRuleModeAlways + + // EmailFilterRuleModeMatch means that the associated regular expression must + // match the associated value. + EmailFilterRuleModeMatch + + // EmailFilterRuleModeNoMatch means that the associated regular expression must NOT + // match the associated value. + EmailFilterRuleModeNoMatch +) + +// string values for each IntegrationEmailFilterRuleMode value +const ( + efrmAlways = "always" // EmailFilterRuleModeAlways + efrmMatch = "match" // EmailFilterRuleModeMatch + efrmNoMatch = "no-match" // EmailFilterRuleModeNoMatch +) + +func (i IntegrationEmailFilterRuleMode) String() string { + switch i { + case EmailFilterRuleModeMatch: + return efrmMatch + + case EmailFilterRuleModeNoMatch: + return efrmNoMatch + + case EmailFilterRuleModeAlways: + return efrmAlways + + default: + return "invalid" + } +} + +// compile time encoding/json interface satisfaction assertions +var ( + _ json.Marshaler = IntegrationEmailFilterRuleMode(0) + _ json.Unmarshaler = (*IntegrationEmailFilterRuleMode)(nil) +) + +// MarshalJSON satisfies json.Marshaler +func (i IntegrationEmailFilterRuleMode) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf("%q", i.String())), nil +} + +// UnmarshalJSON satisfies json.Unmarshaler +func (i *IntegrationEmailFilterRuleMode) UnmarshalJSON(b []byte) error { + if b[0] != '"' { + if bytes.Equal(b, []byte(`null`)) { + return errors.New("value cannot be null") + } + + // just return json.Unmarshal error + var s string + + err := json.Unmarshal(b, &s) + if err == nil { + panic("this should not be possible...") + } + + return err + } + + v := string(b[1 : len(b)-1]) + + switch v { + case efrmMatch: + *i = EmailFilterRuleModeMatch + + case efrmNoMatch: + *i = EmailFilterRuleModeNoMatch + + case efrmAlways: + *i = EmailFilterRuleModeAlways + + default: + return fmt.Errorf("unknown value %q", v) + } + + return nil +} + +// IntegrationEmailFilterRule represents a single email filter rule for an +// integration of type generic_email_inbound_integration. Information about how +// to configure email rules can be found here: +// https://support.pagerduty.com/docs/email-management-filters-and-rules. +type IntegrationEmailFilterRule struct { + // SubjectMode and SubjectRegex control the behaviors of how this filter + // matches the subject of an inbound email. + SubjectMode IntegrationEmailFilterRuleMode `json:"subject_mode,omitempty"` + SubjectRegex *string `json:"subject_regex,omitempty"` + + // BodyMode and BodyRegex control the behaviors of how this filter matches + // the body of an inbound email. + BodyMode IntegrationEmailFilterRuleMode `json:"body_mode,omitempty"` + BodyRegex *string `json:"body_regex,omitempty"` + + FromEmailMode IntegrationEmailFilterRuleMode `json:"from_email_mode,omitempty"` + FromEmailRegex *string `json:"from_email_regex,omitempty"` +} + +// UnmarshalJSON satisfies json.Unmarshaler. +func (i *IntegrationEmailFilterRule) UnmarshalJSON(b []byte) error { + // the purpose of this function is to ensure that when unmarshaling, the + // different *string values are never nil pointers. + // + // this is not a communicated feature of the API, so if it chnages + // it's not a breaking change -- doesn't mean we can't try. + var ief integrationEmailFilterRule + if err := json.Unmarshal(b, &ief); err != nil { + return err + } + + i.BodyMode = ief.BodyMode + i.SubjectMode = ief.SubjectMode + i.FromEmailMode = ief.FromEmailMode + + // if the *string is nil, set it to a *string with value "" + if ief.SubjectRegex == nil { + i.SubjectRegex = new(string) + } else { + i.SubjectRegex = ief.SubjectRegex + } + + if ief.BodyRegex == nil { + i.BodyRegex = new(string) + } else { + i.BodyRegex = ief.BodyRegex + } + + if ief.FromEmailRegex == nil { + i.FromEmailRegex = new(string) + } else { + i.FromEmailRegex = ief.FromEmailRegex + } + + return nil +} + +type integrationEmailFilterRule struct { + SubjectMode IntegrationEmailFilterRuleMode `json:"subject_mode"` + SubjectRegex *string `json:"subject_regex,omitempty"` + BodyMode IntegrationEmailFilterRuleMode `json:"body_mode"` + BodyRegex *string `json:"body_regex,omitempty"` + FromEmailMode IntegrationEmailFilterRuleMode `json:"from_email_mode"` + FromEmailRegex *string `json:"from_email_regex,omitempty"` +} + +// Integration is an endpoint (like Nagios, email, or an API call) that +// generates events, which are normalized and de-duplicated by PagerDuty to +// create incidents. +type Integration struct { + APIObject + Name string `json:"name,omitempty"` + Service *APIObject `json:"service,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + Vendor *APIObject `json:"vendor,omitempty"` + IntegrationKey string `json:"integration_key,omitempty"` + IntegrationEmail string `json:"integration_email,omitempty"` + EmailFilterMode IntegrationEmailFilterMode `json:"email_filter_mode,omitempty"` + EmailFilters []IntegrationEmailFilterRule `json:"email_filters,omitempty"` +} + +// CreateIntegration creates a new integration belonging to a service. +// +// Deprecated: Use CreateIntegrationWithContext instead. +func (c *Client) CreateIntegration(id string, i Integration) (*Integration, error) { + return c.CreateIntegrationWithContext(context.Background(), id, i) +} + +// CreateIntegrationWithContext creates a new integration belonging to a service. +func (c *Client) CreateIntegrationWithContext(ctx context.Context, id string, i Integration) (*Integration, error) { + d := map[string]Integration{ + "integration": i, + } + + resp, err := c.post(ctx, "/services/"+id+"/integrations", d, nil) + return getIntegrationFromResponse(c, resp, err) +} + +// GetIntegrationOptions is the data structure used when calling the GetIntegration API endpoint. +type GetIntegrationOptions struct { + Includes []string `url:"include,omitempty,brackets"` +} + +// GetIntegration gets details about an integration belonging to a service. +// +// Deprecated: Use GetIntegrationWithContext instead. +func (c *Client) GetIntegration(serviceID, integrationID string, o GetIntegrationOptions) (*Integration, error) { + return c.GetIntegrationWithContext(context.Background(), serviceID, integrationID, o) +} + +// GetIntegrationWithContext gets details about an integration belonging to a service. +func (c *Client) GetIntegrationWithContext(ctx context.Context, serviceID, integrationID string, o GetIntegrationOptions) (*Integration, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + + resp, err := c.get(ctx, "/services/"+serviceID+"/integrations/"+integrationID+"?"+v.Encode(), nil) + return getIntegrationFromResponse(c, resp, err) +} + +// UpdateIntegration updates an integration belonging to a service. +// +// Deprecated: Use UpdateIntegrationWithContext instead. +func (c *Client) UpdateIntegration(serviceID string, i Integration) (*Integration, error) { + return c.UpdateIntegrationWithContext(context.Background(), serviceID, i) +} + +// UpdateIntegrationWithContext updates an integration belonging to a service. +func (c *Client) UpdateIntegrationWithContext(ctx context.Context, serviceID string, i Integration) (*Integration, error) { + resp, err := c.put(ctx, "/services/"+serviceID+"/integrations/"+i.ID, i, nil) + return getIntegrationFromResponse(c, resp, err) +} + +// DeleteIntegration deletes an existing integration. +// +// Deprecated: Use DeleteIntegrationWithContext instead. +func (c *Client) DeleteIntegration(serviceID string, integrationID string) error { + return c.DeleteIntegrationWithContext(context.Background(), serviceID, integrationID) +} + +// DeleteIntegrationWithContext deletes an existing integration. +func (c *Client) DeleteIntegrationWithContext(ctx context.Context, serviceID string, integrationID string) error { + _, err := c.delete(ctx, "/services/"+serviceID+"/integrations/"+integrationID) + return err +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/standard.go b/vendor/github.com/PagerDuty/go-pagerduty/standard.go new file mode 100644 index 0000000..490f554 --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/standard.go @@ -0,0 +1,148 @@ +package pagerduty + +import ( + "context" + + "github.com/google/go-querystring/query" +) + +const ( + standardPath = "/standards" +) + +// Standard defines a PagerDuty resource standard. +type Standard struct { + Active bool `json:"active"` + Description string `json:"description,omitempty"` + Exclusions []StandardInclusionExclusion `json:"exclusions,omitempty"` + ID string `json:"id,omitempty"` + Inclusions []StandardInclusionExclusion `json:"inclusions,omitempty"` + Name string `json:"name,omitempty"` + ResourceType string `json:"resource_type,omitempty"` + Type string `json:"type,omitempty"` +} + +type StandardInclusionExclusion struct { + Type string `json:"type,omitempty"` + ID string `json:"id,omitempty"` +} + +// ListStandardsResponse is the data structure returned from calling the ListStandards API endpoint. +type ListStandardsResponse struct { + Standards []Standard `json:"standards"` +} + +// ListStandardsOptions is the data structure used when calling the ListStandards API endpoint. +type ListStandardsOptions struct { + Active bool `url:"active,omitempty"` + + // ResourceType query for a specific resource type. + // Allowed value: technical_service + ResourceType string `url:"resource_type,omitempty"` +} + +type ResourceStandardScore struct { + ResourceID string `json:"resource_id,omitempty"` + ResourceType string `json:"resource_type,omitempty"` + Score *ResourceScore `json:"score,omitempty"` + Standards []ResourceStandard `json:"standards,omitempty"` +} + +type ResourceScore struct { + Passing int `json:"passing,omitempty"` + Total int `json:"total,omitempty"` +} + +type ResourceStandard struct { + Active bool `json:"active"` + Description string `json:"description,omitempty"` + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Pass bool `json:"pass"` + Type string `json:"type,omitempty"` +} + +type ListMultiResourcesStandardScoresResponse struct { + Resources []ResourceStandardScore `json:"resources,omitempty"` +} + +type ListMultiResourcesStandardScoresOptions struct { + // Ids of resources to apply the standards. Maximum of 100 items + IDs []string `url:"ids,omitempty,brackets"` +} + +// ListStandards lists all the existing standards. +func (c *Client) ListStandards(ctx context.Context, o ListStandardsOptions) (*ListStandardsResponse, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + + resp, err := c.get(ctx, standardPath+"?"+v.Encode(), nil) + if err != nil { + return nil, err + } + + var result ListStandardsResponse + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil +} + +// UpdateStandard updates an existing standard. +func (c *Client) UpdateStandard(ctx context.Context, id string, s Standard) (*Standard, error) { + resp, err := c.put(ctx, standardPath+"/"+id, s, nil) + if err != nil { + return nil, err + } + + var result Standard + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil +} + +// ListResourceStandardScores +// +// rt - Resource type +// Allowed values: technical_services +func (c *Client) ListResourceStandardScores(ctx context.Context, id string, rt string) (*ResourceStandardScore, error) { + resp, err := c.get(ctx, standardPath+"/scores/"+rt+"/"+id, nil) + if err != nil { + return nil, err + } + + var result ResourceStandardScore + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil +} + +// ListMultiResourcesStandardScores +// +// rt - Resource type +// Allowed values: technical_services +func (c *Client) ListMultiResourcesStandardScores(ctx context.Context, rt string, o ListMultiResourcesStandardScoresOptions) (*ListMultiResourcesStandardScoresResponse, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + + resp, err := c.get(ctx, standardPath+"/scores/"+rt+"?"+v.Encode(), nil) + if err != nil { + return nil, err + } + + var result ListMultiResourcesStandardScoresResponse + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/tag.go b/vendor/github.com/PagerDuty/go-pagerduty/tag.go new file mode 100644 index 0000000..f33a7d8 --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/tag.go @@ -0,0 +1,389 @@ +package pagerduty + +import ( + "context" + "fmt" + "net/http" + + "github.com/google/go-querystring/query" +) + +// Tag is a way to label user, team and escalation policies in PagerDuty +type Tag struct { + APIObject + Label string `json:"label,omitempty"` +} + +// ListTagResponse is the structure used when calling the ListTags API endpoint. +type ListTagResponse struct { + APIListObject + Tags []*Tag `json:"tags"` +} + +// ListUserResponse is the structure used to list users assigned a given tag +type ListUserResponse struct { + APIListObject + Users []*APIObject `json:"users,omitempty"` +} + +// ListTeamsForTagResponse is the structure used to list teams assigned a given tag +type ListTeamsForTagResponse struct { + APIListObject + Teams []*APIObject `json:"teams,omitempty"` +} + +// ListEPResponse is the structure used to list escalation policies assigned a given tag +type ListEPResponse struct { + APIListObject + EscalationPolicies []*APIObject `json:"escalation_policies,omitempty"` +} + +// ListTagOptions are the input parameters used when calling the ListTags API endpoint. +type ListTagOptions struct { + // Limit is the pagination parameter that limits the number of results per + // page. PagerDuty defaults this value to 25 if omitted, and sets an upper + // bound of 100. + Limit uint `url:"limit,omitempty"` + + // Offset is the pagination parameter that specifies the offset at which to + // start pagination results. When trying to request the next page of + // results, the new Offset value should be currentOffset + Limit. + Offset uint `url:"offset,omitempty"` + + // Total is the pagination parameter to request that the API return the + // total count of items in the response. If this field is omitted or set to + // false, the total number of results will not be sent back from the PagerDuty API. + // + // Setting this to true will slow down the API response times, and so it's + // recommended to omit it unless you've a specific reason for wanting the + // total count of items in the collection. + Total bool `url:"total,omitempty"` + + Query string `url:"query,omitempty"` +} + +// TagAssignments can be applied teams, users and escalation policies +type TagAssignments struct { + Add []*TagAssignment `json:"add,omitempty"` + Remove []*TagAssignment `json:"remove,omitempty"` +} + +// TagAssignment is the structure for assigning tags to an entity +type TagAssignment struct { + Type string `json:"type"` + TagID string `json:"id,omitempty"` + Label string `json:"label,omitempty"` +} + +// ListTags lists tags on your PagerDuty account, optionally filtered by a +// search query. This method currently handles pagination of the response, so +// all tags matched should be present. +// +// Please note that the automatic pagination will be removed in v2 of this +// package, so it's recommended to use ListTagsPaginated() instead. +func (c *Client) ListTags(o ListTagOptions) (*ListTagResponse, error) { + tags, err := c.ListTagsPaginated(context.Background(), o) + if err != nil { + return nil, err + } + + return &ListTagResponse{Tags: tags}, nil +} + +// ListTagsPaginated lists tags on your PagerDuty account, optionally filtered by a search query. +func (c *Client) ListTagsPaginated(ctx context.Context, o ListTagOptions) ([]*Tag, error) { + tags, err := getTagList(ctx, c, "", "", o) + if err != nil { + return nil, err + } + + return tags, nil +} + +// CreateTag creates a new tag. +// +// Deprecated: Use CreateTagWithContext instead. +func (c *Client) CreateTag(t *Tag) (*Tag, error) { + return c.CreateTagWithContext(context.Background(), t) +} + +// CreateTagWithContext creates a new tag. +func (c *Client) CreateTagWithContext(ctx context.Context, t *Tag) (*Tag, error) { + d := map[string]*Tag{ + "tag": t, + } + + resp, err := c.post(ctx, "/tags", d, nil) + return getTagFromResponse(c, resp, err) +} + +// DeleteTag removes an existing tag. +// +// Deprecated: Use DeleteTagWithContext instead. +func (c *Client) DeleteTag(id string) error { + return c.DeleteTagWithContext(context.Background(), id) +} + +// DeleteTagWithContext removes an existing tag. +func (c *Client) DeleteTagWithContext(ctx context.Context, id string) error { + _, err := c.delete(ctx, "/tags/"+id) + return err +} + +// GetTag gets details about an existing tag. +// +// Deprecated: Use GetTagWithContext instead. +func (c *Client) GetTag(id string) (*Tag, error) { + return c.GetTagWithContext(context.Background(), id) +} + +// GetTagWithContext gets details about an existing tag. +func (c *Client) GetTagWithContext(ctx context.Context, id string) (*Tag, error) { + resp, err := c.get(ctx, "/tags/"+id, nil) + return getTagFromResponse(c, resp, err) +} + +// AssignTags adds and removes tag assignments with entities. +// +// Deprecated: Use AssignTagsWithContext instead. +func (c *Client) AssignTags(e, eid string, a *TagAssignments) error { + return c.AssignTagsWithContext(context.Background(), e, eid, a) +} + +// AssignTagsWithContext adds and removes tag assignments with entities. +// Permitted entity types are users, teams, and escalation_policies. +func (c *Client) AssignTagsWithContext(ctx context.Context, entityType, entityID string, a *TagAssignments) error { + _, err := c.post(ctx, "/"+entityType+"/"+entityID+"/change_tags", a, nil) + if err != nil { + return err + } + + return nil +} + +// GetUsersByTag gets related user references based on the Tag. This method +// currently handles pagination of the response, so all user references with the +// tag should be present. +// +// Please note that the automatic pagination will be removed in v2 of this +// package, so it's recommended to use GetUsersByTagPaginated() instead. +func (c *Client) GetUsersByTag(tid string) (*ListUserResponse, error) { + objs, err := c.GetUsersByTagPaginated(context.Background(), tid) + if err != nil { + return nil, err + } + + return &ListUserResponse{Users: objs}, nil +} + +// GetUsersByTagPaginated gets related user references based on the tag. To get the +// full info of the user, you will need to iterate over the returned slice +// and get that user's details. +func (c *Client) GetUsersByTagPaginated(ctx context.Context, tagID string) ([]*APIObject, error) { + var users []*APIObject + + // Create a handler closure capable of parsing data from the business_services endpoint + // and appending resultant business_services to the return slice. + responseHandler := func(response *http.Response) (APIListObject, error) { + var result ListUserResponse + if err := c.decodeJSON(response, &result); err != nil { + return APIListObject{}, err + } + + users = append(users, result.Users...) + + // Return stats on the current page. Caller can use this information to + // adjust for requesting additional pages. + return APIListObject{ + More: result.More, + Offset: result.Offset, + Limit: result.Limit, + }, nil + } + + // Make call to get all pages associated with the base endpoint. + if err := c.pagedGet(ctx, "/tags/"+tagID+"/users/", responseHandler); err != nil { + return nil, err + } + + return users, nil +} + +// GetTeamsByTag gets related teams based on the tag. This method currently +// handles pagination of the response, so all team references with the tag +// should be present. +// +// Please note that the automatic pagination will be removed in v2 of this +// package, so it's recommended to use GetTeamsByTagPaginated() instead. +func (c *Client) GetTeamsByTag(tid string) (*ListTeamsForTagResponse, error) { + objs, err := c.GetTeamsByTagPaginated(context.Background(), tid) + if err != nil { + return nil, err + } + + return &ListTeamsForTagResponse{Teams: objs}, nil +} + +// GetTeamsByTagPaginated gets related teams based on the tag. To get the full +// info of the team, you will need to iterate over the returend slice and get +// that team's details. +func (c *Client) GetTeamsByTagPaginated(ctx context.Context, tagID string) ([]*APIObject, error) { + var teams []*APIObject + + // Create a handler closure capable of parsing data from the business_services endpoint + // and appending resultant business_services to the return slice. + responseHandler := func(response *http.Response) (APIListObject, error) { + var result ListTeamsForTagResponse + if err := c.decodeJSON(response, &result); err != nil { + return APIListObject{}, err + } + + teams = append(teams, result.Teams...) + + // Return stats on the current page. Caller can use this information to + // adjust for requesting additional pages. + return APIListObject{ + More: result.More, + Offset: result.Offset, + Limit: result.Limit, + }, nil + } + + // Make call to get all pages associated with the base endpoint. + if err := c.pagedGet(ctx, "/tags/"+tagID+"/teams/", responseHandler); err != nil { + return nil, err + } + + return teams, nil +} + +// GetEscalationPoliciesByTag gets related escalation policies based on the tag. +// This method currently handles pagination of the response, so all escalation +// policy references with the tag should be present. +// +// Please note that the automatic pagination will be removed in v2 of this +// package, so it's recommended to use GetEscalationPoliciesByTagPaginated() +// instead. +func (c *Client) GetEscalationPoliciesByTag(tid string) (*ListEPResponse, error) { + objs, err := c.GetEscalationPoliciesByTagPaginated(context.Background(), tid) + if err != nil { + return nil, err + } + + return &ListEPResponse{EscalationPolicies: objs}, nil +} + +// GetEscalationPoliciesByTagPaginated gets related escalation policies based on +// the tag. To get the full info of the EP, you will need to iterate over the +// returend slice and get that policy's details. +func (c *Client) GetEscalationPoliciesByTagPaginated(ctx context.Context, tagID string) ([]*APIObject, error) { + var eps []*APIObject + + // Create a handler closure capable of parsing data from the business_services endpoint + // and appending resultant business_services to the return slice. + responseHandler := func(response *http.Response) (APIListObject, error) { + var result ListEPResponse + if err := c.decodeJSON(response, &result); err != nil { + return APIListObject{}, err + } + + eps = append(eps, result.EscalationPolicies...) + + // Return stats on the current page. Caller can use this information to + // adjust for requesting additional pages. + return APIListObject{ + More: result.More, + Offset: result.Offset, + Limit: result.Limit, + }, nil + } + + // Make call to get all pages associated with the base endpoint. + if err := c.pagedGet(ctx, "/tags/"+tagID+"/escalation_policies/", responseHandler); err != nil { + return nil, err + } + + return eps, nil +} + +// GetTagsForEntity get related tags for Users, Teams or Escalation Policies. +// This method currently handles pagination of the response, so all tags should +// be present. +// +// Please note that the automatic pagination will be removed in v2 of this +// package, so it's recommended to use GetTagsForEntityPaginated() instead. +func (c *Client) GetTagsForEntity(entityType, entityID string, o ListTagOptions) (*ListTagResponse, error) { + tags, err := c.GetTagsForEntityPaginated(context.Background(), entityType, entityID, o) + if err != nil { + return nil, err + } + + return &ListTagResponse{Tags: tags}, nil +} + +// GetTagsForEntityPaginated gets related tags for Users, Teams or Escalation +// Policies. +func (c *Client) GetTagsForEntityPaginated(ctx context.Context, entityType, entityID string, o ListTagOptions) ([]*Tag, error) { + return getTagList(ctx, c, entityType, entityID, o) +} + +func getTagFromResponse(c *Client, resp *http.Response, err error) (*Tag, error) { + if err != nil { + return nil, err + } + + var target map[string]Tag + if dErr := c.decodeJSON(resp, &target); dErr != nil { + return nil, fmt.Errorf("Could not decode JSON response: %v", dErr) + } + + const rootNode = "tag" + + t, nodeOK := target[rootNode] + if !nodeOK { + return nil, fmt.Errorf("JSON response does not have %s field", rootNode) + } + + return &t, nil +} + +// getTagList is a utility function that processes all pages of a ListTagResponse +func getTagList(ctx context.Context, c *Client, entityType, entityID string, o ListTagOptions) ([]*Tag, error) { + queryParms, err := query.Values(o) + if err != nil { + return nil, err + } + + var tags []*Tag + + // Create a handler closure capable of parsing data from the business_services endpoint + // and appending resultant business_services to the return slice. + responseHandler := func(response *http.Response) (APIListObject, error) { + var result ListTagResponse + if err := c.decodeJSON(response, &result); err != nil { + return APIListObject{}, err + } + + tags = append(tags, result.Tags...) + + // Return stats on the current page. Caller can use this information to + // adjust for requesting additional pages. + return APIListObject{ + More: result.More, + Offset: result.Offset, + Limit: result.Limit, + }, nil + } + + path := "/tags" + if entityType != "" && entityID != "" { + path = "/" + entityType + "/" + entityID + "/tags" + } + + // Make call to get all pages associated with the base endpoint. + if err := c.pagedGet(ctx, path+"?"+queryParms.Encode(), responseHandler); err != nil { + return nil, err + } + + return tags, nil +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/team.go b/vendor/github.com/PagerDuty/go-pagerduty/team.go index 735b928..1a60b82 100644 --- a/vendor/github.com/PagerDuty/go-pagerduty/team.go +++ b/vendor/github.com/PagerDuty/go-pagerduty/team.go @@ -1,6 +1,7 @@ package pagerduty import ( + "context" "fmt" "net/http" @@ -10,8 +11,9 @@ import ( // Team is a collection of users and escalation policies that represent a group of people within an organization. type Team struct { APIObject - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Parent *APIObject `json:"parent,omitempty"` } // ListTeamResponse is the structure used when calling the ListTeams API endpoint. @@ -22,70 +24,189 @@ type ListTeamResponse struct { // ListTeamOptions are the input parameters used when calling the ListTeams API endpoint. type ListTeamOptions struct { - APIListObject + // Limit is the pagination parameter that limits the number of results per + // page. PagerDuty defaults this value to 25 if omitted, and sets an upper + // bound of 100. + Limit uint `url:"limit,omitempty"` + + // Offset is the pagination parameter that specifies the offset at which to + // start pagination results. When trying to request the next page of + // results, the new Offset value should be currentOffset + Limit. + Offset uint `url:"offset,omitempty"` + + // Total is the pagination parameter to request that the API return the + // total count of items in the response. If this field is omitted or set to + // false, the total number of results will not be sent back from the PagerDuty API. + // + // Setting this to true will slow down the API response times, and so it's + // recommended to omit it unless you've a specific reason for wanting the + // total count of items in the collection. + Total bool `url:"total,omitempty"` + Query string `url:"query,omitempty"` } -// ListTeams lists teams of your PagerDuty account, optionally filtered by a search query. +// ListTeams lists teams of your PagerDuty account, optionally filtered by a +// search query. +// +// Deprecated: Use ListTeamsWithContext instead. func (c *Client) ListTeams(o ListTeamOptions) (*ListTeamResponse, error) { + return c.ListTeamsWithContext(context.Background(), o) +} + +// ListTeamsWithContext lists teams of your PagerDuty account, optionally +// filtered by a search query. +func (c *Client) ListTeamsWithContext(ctx context.Context, o ListTeamOptions) (*ListTeamResponse, error) { v, err := query.Values(o) if err != nil { return nil, err } - resp, err := c.get("/teams?" + v.Encode()) + resp, err := c.get(ctx, "/teams?"+v.Encode(), nil) if err != nil { return nil, err } + var result ListTeamResponse - return &result, c.decodeJSON(resp, &result) + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil } // CreateTeam creates a new team. +// +// Deprecated: Use CreateTeamWithContext instead. func (c *Client) CreateTeam(t *Team) (*Team, error) { - resp, err := c.post("/teams", t, nil) + return c.CreateTeamWithContext(context.Background(), t) +} + +// CreateTeamWithContext creates a new team. +func (c *Client) CreateTeamWithContext(ctx context.Context, t *Team) (*Team, error) { + resp, err := c.post(ctx, "/teams", t, nil) return getTeamFromResponse(c, resp, err) } // DeleteTeam removes an existing team. +// +// Deprecated: Use DeleteTeamWithContext instead. func (c *Client) DeleteTeam(id string) error { - _, err := c.delete("/teams/" + id) + return c.DeleteTeamWithContext(context.Background(), id) +} + +// DeleteTeamWithContext removes an existing team. +func (c *Client) DeleteTeamWithContext(ctx context.Context, id string) error { + _, err := c.delete(ctx, "/teams/"+id) return err } // GetTeam gets details about an existing team. +// +// Deprecated: Use GetTeamWithContext instead. func (c *Client) GetTeam(id string) (*Team, error) { - resp, err := c.get("/teams/" + id) + return c.GetTeamWithContext(context.Background(), id) +} + +// GetTeamWithContext gets details about an existing team. +func (c *Client) GetTeamWithContext(ctx context.Context, id string) (*Team, error) { + resp, err := c.get(ctx, "/teams/"+id, nil) return getTeamFromResponse(c, resp, err) } // UpdateTeam updates an existing team. +// +// Deprecated: Use UpdateTeamWithContext instead. func (c *Client) UpdateTeam(id string, t *Team) (*Team, error) { - resp, err := c.put("/teams/"+id, t, nil) + return c.UpdateTeamWithContext(context.Background(), id, t) +} + +// UpdateTeamWithContext updates an existing team. +func (c *Client) UpdateTeamWithContext(ctx context.Context, id string, t *Team) (*Team, error) { + resp, err := c.put(ctx, "/teams/"+id, t, nil) return getTeamFromResponse(c, resp, err) } // RemoveEscalationPolicyFromTeam removes an escalation policy from a team. +// +// Deprecated: Use RemoveEscalationPolicyFromTeamWithContext instead. func (c *Client) RemoveEscalationPolicyFromTeam(teamID, epID string) error { - _, err := c.delete("/teams/" + teamID + "/escalation_policies/" + epID) + return c.RemoveEscalationPolicyFromTeamWithContext(context.Background(), teamID, epID) +} + +// RemoveEscalationPolicyFromTeamWithContext removes an escalation policy from a team. +func (c *Client) RemoveEscalationPolicyFromTeamWithContext(ctx context.Context, teamID, epID string) error { + _, err := c.delete(ctx, "/teams/"+teamID+"/escalation_policies/"+epID) return err } // AddEscalationPolicyToTeam adds an escalation policy to a team. +// +// Deprecated: Use AddEscalationPolicyToTeamWithContext instead. func (c *Client) AddEscalationPolicyToTeam(teamID, epID string) error { - _, err := c.put("/teams/"+teamID+"/escalation_policies/"+epID, nil, nil) + return c.AddEscalationPolicyToTeamWithContext(context.Background(), teamID, epID) +} + +// AddEscalationPolicyToTeamWithContext adds an escalation policy to a team. +func (c *Client) AddEscalationPolicyToTeamWithContext(ctx context.Context, teamID, epID string) error { + _, err := c.put(ctx, "/teams/"+teamID+"/escalation_policies/"+epID, nil, nil) return err } // RemoveUserFromTeam removes a user from a team. +// +// Deprecated: Use RemoveUserFromTeamWithContext instead. func (c *Client) RemoveUserFromTeam(teamID, userID string) error { - _, err := c.delete("/teams/" + teamID + "/users/" + userID) + return c.RemoveUserFromTeamWithContext(context.Background(), teamID, userID) +} + +// RemoveUserFromTeamWithContext removes a user from a team. +func (c *Client) RemoveUserFromTeamWithContext(ctx context.Context, teamID, userID string) error { + _, err := c.delete(ctx, "/teams/"+teamID+"/users/"+userID) return err } // AddUserToTeam adds a user to a team. +// +// Deprecated: Use AddUserToTeamWithContext instead. func (c *Client) AddUserToTeam(teamID, userID string) error { - _, err := c.put("/teams/"+teamID+"/users/"+userID, nil, nil) + return c.AddUserToTeamWithContext(context.Background(), AddUserToTeamOptions{TeamID: teamID, UserID: userID}) +} + +// TeamUserRole is a named type to represent the different Team Roles supported +// by PagerDuty when adding a user to a team. +// +// For more info: https://support.pagerduty.com/docs/advanced-permissions#team-roles +type TeamUserRole string + +const ( + // TeamUserRoleObserver is the obesrver team role, which generally provides + // read-only access. They gain responder-level permissions on an incident if + // one is assigned to them. + TeamUserRoleObserver TeamUserRole = "observer" + + // TeamUserRoleResponder is the responder team role, and they are given the + // same permissions as the observer plus the ability to respond to + // incidents, trigger incidents, and manage overrides. + TeamUserRoleResponder TeamUserRole = "responder" + + // TeamUserRoleManager is the manager team role, and they are given the same + // permissions as the responder plus the ability to edit and delete the + // different resources owned by the team. + TeamUserRoleManager TeamUserRole = "manager" +) + +// AddUserToTeamOptions is an option struct for the AddUserToTeamWithContext +// method. +type AddUserToTeamOptions struct { + TeamID string `json:"-"` + UserID string `json:"-"` + Role TeamUserRole `json:"role,omitempty"` +} + +// AddUserToTeamWithContext adds a user to a team. +func (c *Client) AddUserToTeamWithContext(ctx context.Context, o AddUserToTeamOptions) error { + _, err := c.put(ctx, "/teams/"+o.TeamID+"/users/"+o.UserID, o, nil) return err } @@ -93,60 +214,124 @@ func getTeamFromResponse(c *Client, resp *http.Response, err error) (*Team, erro if err != nil { return nil, err } + var target map[string]Team if dErr := c.decodeJSON(resp, &target); dErr != nil { return nil, fmt.Errorf("Could not decode JSON response: %v", dErr) } - rootNode := "team" + + const rootNode = "team" + t, nodeOK := target[rootNode] if !nodeOK { return nil, fmt.Errorf("JSON response does not have %s field", rootNode) } + return &t, nil } // Member is a team member. type Member struct { - APIObject struct { - APIObject - } `json:"user"` - Role string `json:"role"` + User APIObject `json:"user"` + Role string `json:"role"` } -// ListMembersOptions are the optional parameters for a members request. -type ListMembersOptions struct { - APIListObject +// ListTeamMembersOptions are the optional parameters for a members request. +type ListTeamMembersOptions struct { + // Limit is the pagination parameter that limits the number of results per + // page. PagerDuty defaults this value to 25 if omitted, and sets an upper + // bound of 100. + Limit uint `url:"limit,omitempty"` + + // Offset is the pagination parameter that specifies the offset at which to + // start pagination results. When trying to request the next page of + // results, the new Offset value should be currentOffset + Limit. + Offset uint `url:"offset,omitempty"` + + // Total is the pagination parameter to request that the API return the + // total count of items in the response. If this field is omitted or set to + // false, the total number of results will not be sent back from the PagerDuty API. + // + // Setting this to true will slow down the API response times, and so it's + // recommended to omit it unless you've a specific reason for wanting the + // total count of items in the collection. + Total bool `url:"total,omitempty"` } -// ListMembersResponse is the response from the members endpoint. -type ListMembersResponse struct { +// ListMembersOptions is the original type name and is retained as an alias for +// API compatibility. +// +// Deprecated: Use type ListTeamMembersOptions instead; will be removed in V2 +type ListMembersOptions = ListTeamMembersOptions + +// ListTeamMembersResponse is the response from the members endpoint. +type ListTeamMembersResponse struct { APIListObject Members []Member `json:"members"` } -// ListMembers gets the first page of users associated with the specified team. -func (c *Client) ListMembers(teamID string, o ListMembersOptions) (*ListMembersResponse, error) { +// ListMembersResponse is the original type name and is retained as an alias for +// API compatibility. +// +// Deprecated: Use type ListTeamMembersResponse instead; will be removed in V2 +type ListMembersResponse = ListTeamMembersResponse + +// ListMembers gets a page of users associated with the specified team. +// +// Deprecated: Use ListTeamMembers instead. +func (c *Client) ListMembers(teamID string, o ListTeamMembersOptions) (*ListTeamMembersResponse, error) { + return c.ListTeamMembers(context.Background(), teamID, o) +} + +// ListMembersWithContext gets a page of users associated with the specified team. +// +// Deprecated: Use ListTeamMembers instead. +func (c *Client) ListMembersWithContext(ctx context.Context, teamID string, o ListTeamMembersOptions) (*ListTeamMembersResponse, error) { + return c.ListTeamMembers(ctx, teamID, o) +} + +// ListTeamMembers gets a page of users associated with the specified team. +func (c *Client) ListTeamMembers(ctx context.Context, teamID string, o ListTeamMembersOptions) (*ListTeamMembersResponse, error) { v, err := query.Values(o) if err != nil { return nil, err } - resp, err := c.get("/teams/" + teamID + "/members?" + v.Encode()) + resp, err := c.get(ctx, "/teams/"+teamID+"/members?"+v.Encode(), nil) if err != nil { return nil, err } - var result ListMembersResponse - return &result, c.decodeJSON(resp, &result) + + var result ListTeamMembersResponse + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil } // ListAllMembers gets all members associated with the specified team. +// +// Deprecated: Use ListTeamMembersPaginated instead. func (c *Client) ListAllMembers(teamID string) ([]Member, error) { - members := make([]Member, 0) + return c.ListTeamMembersPaginated(context.Background(), teamID) +} + +// ListMembersPaginated gets all members associated with the specified team. +// +// Deprecated: Use ListTeamMembersPaginated instead. +func (c *Client) ListMembersPaginated(ctx context.Context, teamID string) ([]Member, error) { + return c.ListTeamMembersPaginated(ctx, teamID) +} + +// ListTeamMembersPaginated gets all members associated with the specified team. +func (c *Client) ListTeamMembersPaginated(ctx context.Context, teamID string) ([]Member, error) { + var members []Member // Create a handler closure capable of parsing data from the members endpoint // and appending resultant members to the return slice. responseHandler := func(response *http.Response) (APIListObject, error) { - var result ListMembersResponse + var result ListTeamMembersResponse if err := c.decodeJSON(response, &result); err != nil { return APIListObject{}, err } @@ -163,7 +348,7 @@ func (c *Client) ListAllMembers(teamID string) ([]Member, error) { } // Make call to get all pages associated with the base endpoint. - if err := c.pagedGet("/teams/"+teamID+"/members", responseHandler); err != nil { + if err := c.pagedGet(ctx, "/teams/"+teamID+"/members", responseHandler); err != nil { return nil, err } diff --git a/vendor/github.com/PagerDuty/go-pagerduty/user.go b/vendor/github.com/PagerDuty/go-pagerduty/user.go index b3cebc7..841dfe7 100644 --- a/vendor/github.com/PagerDuty/go-pagerduty/user.go +++ b/vendor/github.com/PagerDuty/go-pagerduty/user.go @@ -1,6 +1,7 @@ package pagerduty import ( + "context" "fmt" "net/http" @@ -9,20 +10,21 @@ import ( // NotificationRule is a rule for notifying the user. type NotificationRule struct { - ID string `json:"id"` + ID string `json:"id,omitempty"` + Type string `json:"type,omitempty"` + Summary string `json:"summary,omitempty"` + Self string `json:"self,omitempty"` + HTMLURL string `json:"html_url,omitempty"` StartDelayInMinutes uint `json:"start_delay_in_minutes"` CreatedAt string `json:"created_at"` ContactMethod ContactMethod `json:"contact_method"` Urgency string `json:"urgency"` - Type string `json:"type"` } // User is a member of a PagerDuty account that has the ability to interact with incidents and other data on the account. type User struct { APIObject - Type string `json:"type"` Name string `json:"name"` - Summary string `json:"summary"` Email string `json:"email"` Timezone string `json:"time_zone,omitempty"` Color string `json:"color,omitempty"` @@ -30,18 +32,19 @@ type User struct { AvatarURL string `json:"avatar_url,omitempty"` Description string `json:"description,omitempty"` InvitationSent bool `json:"invitation_sent,omitempty"` - ContactMethods []ContactMethod `json:"contact_methods"` - NotificationRules []NotificationRule `json:"notification_rules"` + ContactMethods []ContactMethod `json:"contact_methods,omitempty"` + NotificationRules []NotificationRule `json:"notification_rules,omitempty"` JobTitle string `json:"job_title,omitempty"` - Teams []Team + Teams []Team `json:"teams,omitempty"` } // ContactMethod is a way of contacting the user. type ContactMethod struct { - ID string `json:"id"` - Type string `json:"type"` - Summary string `json:"summary"` - Self string `json:"self"` + ID string `json:"id,omitempty"` + Type string `json:"type,omitempty"` + Summary string `json:"summary,omitempty"` + Self string `json:"self,omitempty"` + HTMLURL string `json:"html_url,omitempty"` Label string `json:"label"` Address string `json:"address"` SendShortEmail bool `json:"send_short_email,omitempty"` @@ -49,18 +52,35 @@ type ContactMethod struct { Blacklisted bool `json:"blacklisted,omitempty"` CountryCode int `json:"country_code,omitempty"` Enabled bool `json:"enabled,omitempty"` - HTMLUrl string `json:"html_url"` } // ListUsersResponse is the data structure returned from calling the ListUsers API endpoint. type ListUsersResponse struct { APIListObject - Users []User + Users []User `json:"users"` } // ListUsersOptions is the data structure used when calling the ListUsers API endpoint. type ListUsersOptions struct { - APIListObject + // Limit is the pagination parameter that limits the number of results per + // page. PagerDuty defaults this value to 25 if omitted, and sets an upper + // bound of 100. + Limit uint `url:"limit,omitempty"` + + // Offset is the pagination parameter that specifies the offset at which to + // start pagination results. When trying to request the next page of + // results, the new Offset value should be currentOffset + Limit. + Offset uint `url:"offset,omitempty"` + + // Total is the pagination parameter to request that the API return the + // total count of items in the response. If this field is omitted or set to + // false, the total number of results will not be sent back from the PagerDuty API. + // + // Setting this to true will slow down the API response times, and so it's + // recommended to omit it unless you've a specific reason for wanting the + // total count of items in the collection. + Total bool `url:"total,omitempty"` + Query string `url:"query,omitempty"` TeamIDs []string `url:"team_ids,omitempty,brackets"` Includes []string `url:"include,omitempty,brackets"` @@ -72,6 +92,12 @@ type ListContactMethodsResponse struct { ContactMethods []ContactMethod `json:"contact_methods"` } +// ListUserNotificationRulesResponse the data structure returned from calling the ListNotificationRules API endpoint. +type ListUserNotificationRulesResponse struct { + APIListObject + NotificationRules []NotificationRule `json:"notification_rules"` +} + // GetUserOptions is the data structure used when calling the GetUser API endpoint. type GetUserOptions struct { Includes []string `url:"include,omitempty,brackets"` @@ -79,62 +105,119 @@ type GetUserOptions struct { // GetCurrentUserOptions is the data structure used when calling the GetCurrentUser API endpoint. type GetCurrentUserOptions struct { - Includes [] string `url:"include,omitempty,brackets"` + Includes []string `url:"include,omitempty,brackets"` } -// ListUsers lists users of your PagerDuty account, optionally filtered by a search query. +// ListUsers lists users of your PagerDuty account, optionally filtered by a +// search query. +// +// Deprecated: Use ListUsersWithContext instead. func (c *Client) ListUsers(o ListUsersOptions) (*ListUsersResponse, error) { + return c.ListUsersWithContext(context.Background(), o) +} + +// ListUsersWithContext lists users of your PagerDuty account, optionally filtered by a search query. +func (c *Client) ListUsersWithContext(ctx context.Context, o ListUsersOptions) (*ListUsersResponse, error) { v, err := query.Values(o) if err != nil { return nil, err } - resp, err := c.get("/users?" + v.Encode()) + + resp, err := c.get(ctx, "/users?"+v.Encode(), nil) if err != nil { return nil, err } + var result ListUsersResponse - return &result, c.decodeJSON(resp, &result) + if err := c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil } // CreateUser creates a new user. +// +// Deprecated: Use CreateUserWithContext instead. func (c *Client) CreateUser(u User) (*User, error) { - data := make(map[string]User) - data["user"] = u - resp, err := c.post("/users", data, nil) + return c.CreateUserWithContext(context.Background(), u) +} + +// CreateUserWithContext creates a new user. +func (c *Client) CreateUserWithContext(ctx context.Context, u User) (*User, error) { + d := map[string]User{ + "user": u, + } + + resp, err := c.post(ctx, "/users", d, nil) return getUserFromResponse(c, resp, err) } // DeleteUser deletes a user. +// +// Deprecated: Use DeleteUserWithContext instead. func (c *Client) DeleteUser(id string) error { - _, err := c.delete("/users/" + id) + return c.DeleteUserWithContext(context.Background(), id) +} + +// DeleteUserWithContext deletes a user. +func (c *Client) DeleteUserWithContext(ctx context.Context, id string) error { + _, err := c.delete(ctx, "/users/"+id) return err } // GetUser gets details about an existing user. +// +// Deprecated: Use GetUserWithContext instead. func (c *Client) GetUser(id string, o GetUserOptions) (*User, error) { + return c.GetUserWithContext(context.Background(), id, o) +} + +// GetUserWithContext gets details about an existing user. +func (c *Client) GetUserWithContext(ctx context.Context, id string, o GetUserOptions) (*User, error) { v, err := query.Values(o) if err != nil { return nil, err } - resp, err := c.get("/users/" + id + "?" + v.Encode()) + + resp, err := c.get(ctx, "/users/"+id+"?"+v.Encode(), nil) return getUserFromResponse(c, resp, err) } // UpdateUser updates an existing user. +// +// Deprecated: Use UpdateUserWithContext instead. func (c *Client) UpdateUser(u User) (*User, error) { - v := make(map[string]User) - v["user"] = u - resp, err := c.put("/users/"+u.ID, v, nil) + return c.UpdateUserWithContext(context.Background(), u) +} + +// UpdateUserWithContext updates an existing user. +func (c *Client) UpdateUserWithContext(ctx context.Context, u User) (*User, error) { + d := map[string]User{ + "user": u, + } + + resp, err := c.put(ctx, "/users/"+u.ID, d, nil) return getUserFromResponse(c, resp, err) } -// GetCurrentUser gets details about the authenticated user when using a user-level API key or OAuth token +// GetCurrentUser gets details about the authenticated user when using a +// user-level API key or OAuth token. +// +// Deprecated: Use GetCurrentUserWithContext instead. func (c *Client) GetCurrentUser(o GetCurrentUserOptions) (*User, error) { + return c.GetCurrentUserWithContext(context.Background(), o) +} + +// GetCurrentUserWithContext gets details about the authenticated user when +// using a user-level API key or OAuth token. +func (c *Client) GetCurrentUserWithContext(ctx context.Context, o GetCurrentUserOptions) (*User, error) { v, err := query.Values(o) if err != nil { return nil, err } - resp, err := c.get("/users/me?" + v.Encode()) + + resp, err := c.get(ctx, "/users/me?"+v.Encode(), nil) return getUserFromResponse(c, resp, err) } @@ -142,53 +225,100 @@ func getUserFromResponse(c *Client, resp *http.Response, err error) (*User, erro if err != nil { return nil, err } + var target map[string]User if dErr := c.decodeJSON(resp, &target); dErr != nil { return nil, fmt.Errorf("Could not decode JSON response: %v", dErr) } - rootNode := "user" + + const rootNode = "user" + t, nodeOK := target[rootNode] if !nodeOK { return nil, fmt.Errorf("JSON response does not have %s field", rootNode) } + return &t, nil } // ListUserContactMethods fetches contact methods of the existing user. +// +// Deprecated: Use ListUserContactMethodsWithContext instead. func (c *Client) ListUserContactMethods(userID string) (*ListContactMethodsResponse, error) { - resp, err := c.get("/users/" + userID + "/contact_methods") + return c.ListUserContactMethodsWithContext(context.Background(), userID) +} + +// ListUserContactMethodsWithContext fetches contact methods of the existing user. +func (c *Client) ListUserContactMethodsWithContext(ctx context.Context, userID string) (*ListContactMethodsResponse, error) { + resp, err := c.get(ctx, "/users/"+userID+"/contact_methods", nil) if err != nil { return nil, err } + var result ListContactMethodsResponse - return &result, c.decodeJSON(resp, &result) + if err := c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil } // GetUserContactMethod gets details about a contact method. +// +// Deprecated: Use GetUserContactMethodWithContext instead. func (c *Client) GetUserContactMethod(userID, contactMethodID string) (*ContactMethod, error) { - resp, err := c.get("/users/" + userID + "/contact_methods/" + contactMethodID) + return c.GetUserContactMethodWithContext(context.Background(), userID, contactMethodID) +} + +// GetUserContactMethodWithContext gets details about a contact method. +func (c *Client) GetUserContactMethodWithContext(ctx context.Context, userID, contactMethodID string) (*ContactMethod, error) { + resp, err := c.get(ctx, "/users/"+userID+"/contact_methods/"+contactMethodID, nil) return getContactMethodFromResponse(c, resp, err) } // DeleteUserContactMethod deletes a user. +// +// Deprecated: Use DeleteUserContactMethodWithContext instead. func (c *Client) DeleteUserContactMethod(userID, contactMethodID string) error { - _, err := c.delete("/users/" + userID + "/contact_methods/" + contactMethodID) + return c.DeleteUserContactMethodWithContext(context.Background(), userID, contactMethodID) +} + +// DeleteUserContactMethodWithContext deletes a user. +func (c *Client) DeleteUserContactMethodWithContext(ctx context.Context, userID, contactMethodID string) error { + _, err := c.delete(ctx, "/users/"+userID+"/contact_methods/"+contactMethodID) return err } // CreateUserContactMethod creates a new contact method for user. +// +// Deprecated: Use CreateUserContactMethodWithContext instead. func (c *Client) CreateUserContactMethod(userID string, cm ContactMethod) (*ContactMethod, error) { - data := make(map[string]ContactMethod) - data["contact_method"] = cm - resp, err := c.post("/users/"+userID+"/contact_methods", data, nil) + return c.CreateUserContactMethodWithContext(context.Background(), userID, cm) +} + +// CreateUserContactMethodWithContext creates a new contact method for user. +func (c *Client) CreateUserContactMethodWithContext(ctx context.Context, userID string, cm ContactMethod) (*ContactMethod, error) { + d := map[string]ContactMethod{ + "contact_method": cm, + } + + resp, err := c.post(ctx, "/users/"+userID+"/contact_methods", d, nil) return getContactMethodFromResponse(c, resp, err) } -// UpdateUserContactMethod updates an existing user. +// UpdateUserContactMethod updates an existing user. It's recommended to use +// UpdateUserContactMethodWithContext instead. func (c *Client) UpdateUserContactMethod(userID string, cm ContactMethod) (*ContactMethod, error) { - v := make(map[string]ContactMethod) - v["contact_method"] = cm - resp, err := c.put("/users/"+userID+"/contact_methods/"+cm.ID, v, nil) + return c.UpdateUserContactMethodWthContext(context.Background(), userID, cm) +} + +// UpdateUserContactMethodWthContext updates an existing user. +func (c *Client) UpdateUserContactMethodWthContext(ctx context.Context, userID string, cm ContactMethod) (*ContactMethod, error) { + d := map[string]ContactMethod{ + "contact_method": cm, + } + + resp, err := c.put(ctx, "/users/"+userID+"/contact_methods/"+cm.ID, d, nil) return getContactMethodFromResponse(c, resp, err) } @@ -196,14 +326,120 @@ func getContactMethodFromResponse(c *Client, resp *http.Response, err error) (*C if err != nil { return nil, err } + var target map[string]ContactMethod if dErr := c.decodeJSON(resp, &target); dErr != nil { return nil, fmt.Errorf("Could not decode JSON response: %v", dErr) } - rootNode := "contact_method" + + const rootNode = "contact_method" + + t, nodeOK := target[rootNode] + if !nodeOK { + return nil, fmt.Errorf("JSON response does not have %s field", rootNode) + } + + return &t, nil +} + +// GetUserNotificationRule gets details about a notification rule. +// +// Deprecated: Use GetUserNotificationRuleWithContext instead. +func (c *Client) GetUserNotificationRule(userID, ruleID string) (*NotificationRule, error) { + return c.GetUserNotificationRuleWithContext(context.Background(), userID, ruleID) +} + +// GetUserNotificationRuleWithContext gets details about a notification rule. +func (c *Client) GetUserNotificationRuleWithContext(ctx context.Context, userID, ruleID string) (*NotificationRule, error) { + resp, err := c.get(ctx, "/users/"+userID+"/notification_rules/"+ruleID, nil) + return getUserNotificationRuleFromResponse(c, resp, err) +} + +// CreateUserNotificationRule creates a new notification rule for a user. +// +// Deprecated: Use CreateUserNotificationRuleWithContext instead. +func (c *Client) CreateUserNotificationRule(userID string, rule NotificationRule) (*NotificationRule, error) { + return c.CreateUserNotificationRuleWithContext(context.Background(), userID, rule) +} + +// CreateUserNotificationRuleWithContext creates a new notification rule for a user. +func (c *Client) CreateUserNotificationRuleWithContext(ctx context.Context, userID string, rule NotificationRule) (*NotificationRule, error) { + d := map[string]NotificationRule{ + "notification_rule": rule, + } + + resp, err := c.post(ctx, "/users/"+userID+"/notification_rules", d, nil) + return getUserNotificationRuleFromResponse(c, resp, err) +} + +// UpdateUserNotificationRule updates a notification rule for a user. +// +// Deprecated: Use UpdateUserNotificationRuleWithContext instead. +func (c *Client) UpdateUserNotificationRule(userID string, rule NotificationRule) (*NotificationRule, error) { + return c.UpdateUserNotificationRuleWithContext(context.Background(), userID, rule) +} + +// UpdateUserNotificationRuleWithContext updates a notification rule for a user. +func (c *Client) UpdateUserNotificationRuleWithContext(ctx context.Context, userID string, rule NotificationRule) (*NotificationRule, error) { + d := map[string]NotificationRule{ + "notification_rule": rule, + } + + resp, err := c.put(ctx, "/users/"+userID+"/notification_rules/"+rule.ID, d, nil) + return getUserNotificationRuleFromResponse(c, resp, err) +} + +// DeleteUserNotificationRule deletes a notification rule for a user. +// +// Deprecated: Use DeleteUserNotificationRuleWithContext instead. +func (c *Client) DeleteUserNotificationRule(userID, ruleID string) error { + return c.DeleteUserNotificationRuleWithContext(context.Background(), userID, ruleID) +} + +// DeleteUserNotificationRuleWithContext deletes a notification rule for a user. +func (c *Client) DeleteUserNotificationRuleWithContext(ctx context.Context, userID, ruleID string) error { + _, err := c.delete(ctx, "/users/"+userID+"/notification_rules/"+ruleID) + return err +} + +// ListUserNotificationRules fetches notification rules of the existing user. +// +// Deprecated: Use ListUserNotificationRulesWithContext instead. +func (c *Client) ListUserNotificationRules(userID string) (*ListUserNotificationRulesResponse, error) { + return c.ListUserNotificationRulesWithContext(context.Background(), userID) +} + +// ListUserNotificationRulesWithContext fetches notification rules of the existing user. +func (c *Client) ListUserNotificationRulesWithContext(ctx context.Context, userID string) (*ListUserNotificationRulesResponse, error) { + resp, err := c.get(ctx, "/users/"+userID+"/notification_rules", nil) + if err != nil { + return nil, err + } + + var result ListUserNotificationRulesResponse + if err := c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil +} + +func getUserNotificationRuleFromResponse(c *Client, resp *http.Response, err error) (*NotificationRule, error) { + if err != nil { + return nil, err + } + + var target map[string]NotificationRule + if dErr := c.decodeJSON(resp, &target); dErr != nil { + return nil, fmt.Errorf("Could not decode JSON response: %v", dErr) + } + + const rootNode = "notification_rule" + t, nodeOK := target[rootNode] if !nodeOK { return nil, fmt.Errorf("JSON response does not have %s field", rootNode) } + return &t, nil } diff --git a/vendor/github.com/PagerDuty/go-pagerduty/vendor.go b/vendor/github.com/PagerDuty/go-pagerduty/vendor.go index cb97160..6c7c6e6 100644 --- a/vendor/github.com/PagerDuty/go-pagerduty/vendor.go +++ b/vendor/github.com/PagerDuty/go-pagerduty/vendor.go @@ -1,6 +1,7 @@ package pagerduty import ( + "context" "fmt" "net/http" @@ -32,31 +33,63 @@ type ListVendorResponse struct { // ListVendorOptions is the data structure used when calling the ListVendors API endpoint. type ListVendorOptions struct { - APIListObject - Query string `url:"query,omitempty"` + // Limit is the pagination parameter that limits the number of results per + // page. PagerDuty defaults this value to 25 if omitted, and sets an upper + // bound of 100. + Limit uint `url:"limit,omitempty"` + + // Offset is the pagination parameter that specifies the offset at which to + // start pagination results. When trying to request the next page of + // results, the new Offset value should be currentOffset + Limit. + Offset uint `url:"offset,omitempty"` + + // Total is the pagination parameter to request that the API return the + // total count of items in the response. If this field is omitted or set to + // false, the total number of results will not be sent back from the PagerDuty API. + // + // Setting this to true will slow down the API response times, and so it's + // recommended to omit it unless you've a specific reason for wanting the + // total count of items in the collection. + Total bool `url:"total,omitempty"` } // ListVendors lists existing vendors. +// +// Deprecated: Use ListVendorsWithContext instead. func (c *Client) ListVendors(o ListVendorOptions) (*ListVendorResponse, error) { - v, err := query.Values(o) + return c.ListVendorsWithContext(context.Background(), o) +} +// ListVendorsWithContext lists existing vendors. +func (c *Client) ListVendorsWithContext(ctx context.Context, o ListVendorOptions) (*ListVendorResponse, error) { + v, err := query.Values(o) if err != nil { return nil, err } - resp, err := c.get("/vendors?" + v.Encode()) - + resp, err := c.get(ctx, "/vendors?"+v.Encode(), nil) if err != nil { return nil, err } var result ListVendorResponse - return &result, c.decodeJSON(resp, &result) + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil } // GetVendor gets details about an existing vendor. +// +// Deprecated: Use GetVendorWithContext instead. func (c *Client) GetVendor(id string) (*Vendor, error) { - resp, err := c.get("/vendors/" + id) + return c.GetVendorWithContext(context.Background(), id) +} + +// GetVendorWithContext gets details about an existing vendor. +func (c *Client) GetVendorWithContext(ctx context.Context, id string) (*Vendor, error) { + resp, err := c.get(ctx, "/vendors/"+id, nil) return getVendorFromResponse(c, resp, err) } @@ -64,14 +97,18 @@ func getVendorFromResponse(c *Client, resp *http.Response, err error) (*Vendor, if err != nil { return nil, err } + var target map[string]Vendor if dErr := c.decodeJSON(resp, &target); dErr != nil { return nil, fmt.Errorf("Could not decode JSON response: %v", dErr) } - rootNode := "vendor" + + const rootNode = "vendor" + t, nodeOK := target[rootNode] if !nodeOK { return nil, fmt.Errorf("JSON response does not have %s field", rootNode) } + return &t, nil } diff --git a/vendor/github.com/PagerDuty/go-pagerduty/webhook.go b/vendor/github.com/PagerDuty/go-pagerduty/webhook.go index 010b1a7..8351ad0 100644 --- a/vendor/github.com/PagerDuty/go-pagerduty/webhook.go +++ b/vendor/github.com/PagerDuty/go-pagerduty/webhook.go @@ -3,33 +3,57 @@ package pagerduty import ( "encoding/json" "io" + "time" ) -// IncidentDetail contains a representation of the incident associated with the action that caused this webhook message. -type IncidentDetail struct { - ID string `json:"id"` - IncidentNumber uint `json:"incident_number"` - CreatedOn string `json:"created_on"` - Status string `json:"status"` - HTMLUrl string `json:"html_url"` - Service string `json:"service"` - AssignedToUser *json.RawMessage `json:"assigned_to_user"` - AssignedTo []string `json:"assigned_to"` - TriggerSummaryData *json.RawMessage `json:"trigger_summary_data"` - TriggerDetailsHTMLUrl string `json:"trigger_details_html_url"` +// IncidentDetails contains a representation of the incident associated with the action that caused this webhook message +type IncidentDetails struct { + APIObject + IncidentNumber int `json:"incident_number"` + Title string `json:"title"` + CreatedAt time.Time `json:"created_at"` + Status string `json:"status"` + IncidentKey *string `json:"incident_key"` + PendingActions []PendingAction `json:"pending_actions"` + Service Service `json:"service"` + Assignments []Assignment `json:"assignments"` + Acknowledgements []Acknowledgement `json:"acknowledgements"` + LastStatusChangeAt time.Time `json:"last_status_change_at"` + LastStatusChangeBy APIObject `json:"last_status_change_by"` + FirstTriggerLogEntry APIObject `json:"first_trigger_log_entry"` + EscalationPolicy APIObject `json:"escalation_policy"` + Teams []APIObject `json:"teams"` + Priority Priority `json:"priority"` + Urgency string `json:"urgency"` + ResolveReason *string `json:"resolve_reason"` + AlertCounts AlertCounts `json:"alert_counts"` + Metadata interface{} `json:"metadata"` + + // Alerts is the list of alerts within this incident. Each item in the slice + // is not fully hydrated, so only the AlertKey field will be set. + Alerts []IncidentAlert `json:"alerts,omitempty"` + + // Description is deprecated, use Title instead. + Description string `json:"description"` +} + +// WebhookPayloadMessages is the wrapper around the Webhook payloads. The Array may contain multiple message elements if webhook firing actions occurred in quick succession +type WebhookPayloadMessages struct { + Messages []WebhookPayload `json:"messages"` } -// WebhookPayload is a single message array for a webhook. +// WebhookPayload represents the V2 webhook payload type WebhookPayload struct { - ID string `json:"id"` - Type string `json:"type"` - CreatedOn string `json:"created_on"` - Data *json.RawMessage `json:"data"` + ID string `json:"id"` + Event string `json:"event"` + CreatedOn time.Time `json:"created_on"` + Incident IncidentDetails `json:"incident"` + LogEntries []LogEntry `json:"log_entries"` } // DecodeWebhook decodes a webhook from a response object. -func DecodeWebhook(r io.Reader) (*WebhookPayload, error) { - var payload WebhookPayload +func DecodeWebhook(r io.Reader) (*WebhookPayloadMessages, error) { + var payload WebhookPayloadMessages if err := json.NewDecoder(r).Decode(&payload); err != nil { return nil, err } diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/equate.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/equate.go index 51ce36f..3d8d0cd 100644 --- a/vendor/github.com/google/go-cmp/cmp/cmpopts/equate.go +++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/equate.go @@ -6,20 +6,21 @@ package cmpopts import ( + "errors" + "fmt" "math" "reflect" "time" "github.com/google/go-cmp/cmp" - "golang.org/x/xerrors" ) func equateAlways(_, _ interface{}) bool { return true } -// EquateEmpty returns a Comparer option that determines all maps and slices +// EquateEmpty returns a [cmp.Comparer] option that determines all maps and slices // with a length of zero to be equal, regardless of whether they are nil. // -// EquateEmpty can be used in conjunction with SortSlices and SortMaps. +// EquateEmpty can be used in conjunction with [SortSlices] and [SortMaps]. func EquateEmpty() cmp.Option { return cmp.FilterValues(isEmpty, cmp.Comparer(equateAlways)) } @@ -31,7 +32,7 @@ func isEmpty(x, y interface{}) bool { (vx.Len() == 0 && vy.Len() == 0) } -// EquateApprox returns a Comparer option that determines float32 or float64 +// EquateApprox returns a [cmp.Comparer] option that determines float32 or float64 // values to be equal if they are within a relative fraction or absolute margin. // This option is not used when either x or y is NaN or infinite. // @@ -42,9 +43,10 @@ func isEmpty(x, y interface{}) bool { // The fraction and margin must be non-negative. // // The mathematical expression used is equivalent to: +// // |x-y| ≤ max(fraction*min(|x|, |y|), margin) // -// EquateApprox can be used in conjunction with EquateNaNs. +// EquateApprox can be used in conjunction with [EquateNaNs]. func EquateApprox(fraction, margin float64) cmp.Option { if margin < 0 || fraction < 0 || math.IsNaN(margin) || math.IsNaN(fraction) { panic("margin or fraction must be a non-negative number") @@ -72,10 +74,10 @@ func (a approximator) compareF32(x, y float32) bool { return a.compareF64(float64(x), float64(y)) } -// EquateNaNs returns a Comparer option that determines float32 and float64 +// EquateNaNs returns a [cmp.Comparer] option that determines float32 and float64 // NaN values to be equal. // -// EquateNaNs can be used in conjunction with EquateApprox. +// EquateNaNs can be used in conjunction with [EquateApprox]. func EquateNaNs() cmp.Option { return cmp.Options{ cmp.FilterValues(areNaNsF64s, cmp.Comparer(equateAlways)), @@ -90,8 +92,8 @@ func areNaNsF32s(x, y float32) bool { return areNaNsF64s(float64(x), float64(y)) } -// EquateApproxTime returns a Comparer option that determines two non-zero -// time.Time values to be equal if they are within some margin of one another. +// EquateApproxTime returns a [cmp.Comparer] option that determines two non-zero +// [time.Time] values to be equal if they are within some margin of one another. // If both times have a monotonic clock reading, then the monotonic time // difference will be used. The margin must be non-negative. func EquateApproxTime(margin time.Duration) cmp.Option { @@ -112,7 +114,7 @@ type timeApproximator struct { func (a timeApproximator) compare(x, y time.Time) bool { // Avoid subtracting times to avoid overflow when the - // difference is larger than the largest representible duration. + // difference is larger than the largest representable duration. if x.After(y) { // Ensure x is always before y x, y = y, x @@ -130,8 +132,8 @@ type anyError struct{} func (anyError) Error() string { return "any error" } func (anyError) Is(err error) bool { return err != nil } -// EquateErrors returns a Comparer option that determines errors to be equal -// if errors.Is reports them to match. The AnyError error can be used to +// EquateErrors returns a [cmp.Comparer] option that determines errors to be equal +// if [errors.Is] reports them to match. The [AnyError] error can be used to // match any non-nil error. func EquateErrors() cmp.Option { return cmp.FilterValues(areConcreteErrors, cmp.Comparer(compareErrors)) @@ -151,6 +153,33 @@ func areConcreteErrors(x, y interface{}) bool { func compareErrors(x, y interface{}) bool { xe := x.(error) ye := y.(error) - // TODO(≥go1.13): Use standard definition of errors.Is. - return xerrors.Is(xe, ye) || xerrors.Is(ye, xe) + return errors.Is(xe, ye) || errors.Is(ye, xe) +} + +// EquateComparable returns a [cmp.Option] that determines equality +// of comparable types by directly comparing them using the == operator in Go. +// The types to compare are specified by passing a value of that type. +// This option should only be used on types that are documented as being +// safe for direct == comparison. For example, [net/netip.Addr] is documented +// as being semantically safe to use with ==, while [time.Time] is documented +// to discourage the use of == on time values. +func EquateComparable(typs ...interface{}) cmp.Option { + types := make(typesFilter) + for _, typ := range typs { + switch t := reflect.TypeOf(typ); { + case !t.Comparable(): + panic(fmt.Sprintf("%T is not a comparable Go type", typ)) + case types[t]: + panic(fmt.Sprintf("%T is already specified", typ)) + default: + types[t] = true + } + } + return cmp.FilterPath(types.filter, cmp.Comparer(equateAny)) } + +type typesFilter map[reflect.Type]bool + +func (tf typesFilter) filter(p cmp.Path) bool { return tf[p.Last().Type()] } + +func equateAny(x, y interface{}) bool { return x == y } diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/ignore.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/ignore.go index 80c6061..fb84d11 100644 --- a/vendor/github.com/google/go-cmp/cmp/cmpopts/ignore.go +++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/ignore.go @@ -14,7 +14,7 @@ import ( "github.com/google/go-cmp/cmp/internal/function" ) -// IgnoreFields returns an Option that ignores fields of the +// IgnoreFields returns an [cmp.Option] that ignores fields of the // given names on a single struct type. It respects the names of exported fields // that are forwarded due to struct embedding. // The struct type is specified by passing in a value of that type. @@ -26,7 +26,7 @@ func IgnoreFields(typ interface{}, names ...string) cmp.Option { return cmp.FilterPath(sf.filter, cmp.Ignore()) } -// IgnoreTypes returns an Option that ignores all values assignable to +// IgnoreTypes returns an [cmp.Option] that ignores all values assignable to // certain types, which are specified by passing in a value of each type. func IgnoreTypes(typs ...interface{}) cmp.Option { tf := newTypeFilter(typs...) @@ -59,10 +59,10 @@ func (tf typeFilter) filter(p cmp.Path) bool { return false } -// IgnoreInterfaces returns an Option that ignores all values or references of +// IgnoreInterfaces returns an [cmp.Option] that ignores all values or references of // values assignable to certain interface types. These interfaces are specified // by passing in an anonymous struct with the interface types embedded in it. -// For example, to ignore sync.Locker, pass in struct{sync.Locker}{}. +// For example, to ignore [sync.Locker], pass in struct{sync.Locker}{}. func IgnoreInterfaces(ifaces interface{}) cmp.Option { tf := newIfaceFilter(ifaces) return cmp.FilterPath(tf.filter, cmp.Ignore()) @@ -107,7 +107,7 @@ func (tf ifaceFilter) filter(p cmp.Path) bool { return false } -// IgnoreUnexported returns an Option that only ignores the immediate unexported +// IgnoreUnexported returns an [cmp.Option] that only ignores the immediate unexported // fields of a struct, including anonymous fields of unexported types. // In particular, unexported fields within the struct's exported fields // of struct types, including anonymous fields, will not be ignored unless the @@ -115,7 +115,7 @@ func (tf ifaceFilter) filter(p cmp.Path) bool { // // Avoid ignoring unexported fields of a type which you do not control (i.e. a // type from another repository), as changes to the implementation of such types -// may change how the comparison behaves. Prefer a custom Comparer instead. +// may change how the comparison behaves. Prefer a custom [cmp.Comparer] instead. func IgnoreUnexported(typs ...interface{}) cmp.Option { ux := newUnexportedFilter(typs...) return cmp.FilterPath(ux.filter, cmp.Ignore()) @@ -148,7 +148,7 @@ func isExported(id string) bool { return unicode.IsUpper(r) } -// IgnoreSliceElements returns an Option that ignores elements of []V. +// IgnoreSliceElements returns an [cmp.Option] that ignores elements of []V. // The discard function must be of the form "func(T) bool" which is used to // ignore slice elements of type V, where V is assignable to T. // Elements are ignored if the function reports true. @@ -176,7 +176,7 @@ func IgnoreSliceElements(discardFunc interface{}) cmp.Option { }, cmp.Ignore()) } -// IgnoreMapEntries returns an Option that ignores entries of map[K]V. +// IgnoreMapEntries returns an [cmp.Option] that ignores entries of map[K]V. // The discard function must be of the form "func(T, R) bool" which is used to // ignore map entries of type K and V, where K and V are assignable to T and R. // Entries are ignored if the function reports true. diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go index a646d74..c6d09da 100644 --- a/vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go +++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go @@ -13,19 +13,19 @@ import ( "github.com/google/go-cmp/cmp/internal/function" ) -// SortSlices returns a Transformer option that sorts all []V. +// SortSlices returns a [cmp.Transformer] option that sorts all []V. // The less function must be of the form "func(T, T) bool" which is used to // sort any slice with element type V that is assignable to T. // // The less function must be: -// • Deterministic: less(x, y) == less(x, y) -// • Irreflexive: !less(x, x) -// • Transitive: if !less(x, y) and !less(y, z), then !less(x, z) +// - Deterministic: less(x, y) == less(x, y) +// - Irreflexive: !less(x, x) +// - Transitive: if !less(x, y) and !less(y, z), then !less(x, z) // // The less function does not have to be "total". That is, if !less(x, y) and // !less(y, x) for two elements x and y, their relative order is maintained. // -// SortSlices can be used in conjunction with EquateEmpty. +// SortSlices can be used in conjunction with [EquateEmpty]. func SortSlices(lessFunc interface{}) cmp.Option { vf := reflect.ValueOf(lessFunc) if !function.IsType(vf.Type(), function.Less) || vf.IsNil() { @@ -82,21 +82,21 @@ func (ss sliceSorter) less(v reflect.Value, i, j int) bool { return ss.fnc.Call([]reflect.Value{vx, vy})[0].Bool() } -// SortMaps returns a Transformer option that flattens map[K]V types to be a +// SortMaps returns a [cmp.Transformer] option that flattens map[K]V types to be a // sorted []struct{K, V}. The less function must be of the form // "func(T, T) bool" which is used to sort any map with key K that is // assignable to T. // -// Flattening the map into a slice has the property that cmp.Equal is able to -// use Comparers on K or the K.Equal method if it exists. +// Flattening the map into a slice has the property that [cmp.Equal] is able to +// use [cmp.Comparer] options on K or the K.Equal method if it exists. // // The less function must be: -// • Deterministic: less(x, y) == less(x, y) -// • Irreflexive: !less(x, x) -// • Transitive: if !less(x, y) and !less(y, z), then !less(x, z) -// • Total: if x != y, then either less(x, y) or less(y, x) +// - Deterministic: less(x, y) == less(x, y) +// - Irreflexive: !less(x, x) +// - Transitive: if !less(x, y) and !less(y, z), then !less(x, z) +// - Total: if x != y, then either less(x, y) or less(y, x) // -// SortMaps can be used in conjunction with EquateEmpty. +// SortMaps can be used in conjunction with [EquateEmpty]. func SortMaps(lessFunc interface{}) cmp.Option { vf := reflect.ValueOf(lessFunc) if !function.IsType(vf.Type(), function.Less) || vf.IsNil() { diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/struct_filter.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/struct_filter.go index a09829c..ca11a40 100644 --- a/vendor/github.com/google/go-cmp/cmp/cmpopts/struct_filter.go +++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/struct_filter.go @@ -67,12 +67,14 @@ func (sf structFilter) filter(p cmp.Path) bool { // fieldTree represents a set of dot-separated identifiers. // // For example, inserting the following selectors: +// // Foo // Foo.Bar.Baz // Foo.Buzz // Nuka.Cola.Quantum // // Results in a tree of the form: +// // {sub: { // "Foo": {ok: true, sub: { // "Bar": {sub: { diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/xform.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/xform.go index 4eb49d6..25b4bd0 100644 --- a/vendor/github.com/google/go-cmp/cmp/cmpopts/xform.go +++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/xform.go @@ -19,15 +19,16 @@ func (xf xformFilter) filter(p cmp.Path) bool { return true } -// AcyclicTransformer returns a Transformer with a filter applied that ensures +// AcyclicTransformer returns a [cmp.Transformer] with a filter applied that ensures // that the transformer cannot be recursively applied upon its own output. // // An example use case is a transformer that splits a string by lines: +// // AcyclicTransformer("SplitLines", func(s string) []string{ // return strings.Split(s, "\n") // }) // -// Had this been an unfiltered Transformer instead, this would result in an +// Had this been an unfiltered [cmp.Transformer] instead, this would result in an // infinite cycle converting a string to []string to [][]string and so on. func AcyclicTransformer(name string, xformFunc interface{}) cmp.Option { xf := xformFilter{cmp.Transformer(name, xformFunc)} diff --git a/vendor/github.com/google/go-cmp/cmp/compare.go b/vendor/github.com/google/go-cmp/cmp/compare.go index 86d0903..0f5b8a4 100644 --- a/vendor/github.com/google/go-cmp/cmp/compare.go +++ b/vendor/github.com/google/go-cmp/cmp/compare.go @@ -5,7 +5,7 @@ // Package cmp determines equality of values. // // This package is intended to be a more powerful and safer alternative to -// reflect.DeepEqual for comparing whether two values are semantically equal. +// [reflect.DeepEqual] for comparing whether two values are semantically equal. // It is intended to only be used in tests, as performance is not a goal and // it may panic if it cannot compare the values. Its propensity towards // panicking means that its unsuitable for production environments where a @@ -13,21 +13,22 @@ // // The primary features of cmp are: // -// • When the default behavior of equality does not suit the needs of the test, -// custom equality functions can override the equality operation. -// For example, an equality function may report floats as equal so long as they -// are within some tolerance of each other. +// - When the default behavior of equality does not suit the test's needs, +// custom equality functions can override the equality operation. +// For example, an equality function may report floats as equal so long as +// they are within some tolerance of each other. // -// • Types that have an Equal method may use that method to determine equality. -// This allows package authors to determine the equality operation for the types -// that they define. +// - Types with an Equal method (e.g., [time.Time.Equal]) may use that method +// to determine equality. This allows package authors to determine +// the equality operation for the types that they define. // -// • If no custom equality functions are used and no Equal method is defined, -// equality is determined by recursively comparing the primitive kinds on both -// values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported -// fields are not compared by default; they result in panics unless suppressed -// by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly -// compared using the Exporter option. +// - If no custom equality functions are used and no Equal method is defined, +// equality is determined by recursively comparing the primitive kinds on +// both values, much like [reflect.DeepEqual]. Unlike [reflect.DeepEqual], +// unexported fields are not compared by default; they result in panics +// unless suppressed by using an [Ignore] option +// (see [github.com/google/go-cmp/cmp/cmpopts.IgnoreUnexported]) +// or explicitly compared using the [Exporter] option. package cmp import ( @@ -36,50 +37,52 @@ import ( "strings" "github.com/google/go-cmp/cmp/internal/diff" - "github.com/google/go-cmp/cmp/internal/flags" "github.com/google/go-cmp/cmp/internal/function" "github.com/google/go-cmp/cmp/internal/value" ) +// TODO(≥go1.18): Use any instead of interface{}. + // Equal reports whether x and y are equal by recursively applying the // following rules in the given order to x and y and all of their sub-values: // -// • Let S be the set of all Ignore, Transformer, and Comparer options that -// remain after applying all path filters, value filters, and type filters. -// If at least one Ignore exists in S, then the comparison is ignored. -// If the number of Transformer and Comparer options in S is greater than one, -// then Equal panics because it is ambiguous which option to use. -// If S contains a single Transformer, then use that to transform the current -// values and recursively call Equal on the output values. -// If S contains a single Comparer, then use that to compare the current values. -// Otherwise, evaluation proceeds to the next rule. +// - Let S be the set of all [Ignore], [Transformer], and [Comparer] options that +// remain after applying all path filters, value filters, and type filters. +// If at least one [Ignore] exists in S, then the comparison is ignored. +// If the number of [Transformer] and [Comparer] options in S is non-zero, +// then Equal panics because it is ambiguous which option to use. +// If S contains a single [Transformer], then use that to transform +// the current values and recursively call Equal on the output values. +// If S contains a single [Comparer], then use that to compare the current values. +// Otherwise, evaluation proceeds to the next rule. // -// • If the values have an Equal method of the form "(T) Equal(T) bool" or -// "(T) Equal(I) bool" where T is assignable to I, then use the result of -// x.Equal(y) even if x or y is nil. Otherwise, no such method exists and -// evaluation proceeds to the next rule. +// - If the values have an Equal method of the form "(T) Equal(T) bool" or +// "(T) Equal(I) bool" where T is assignable to I, then use the result of +// x.Equal(y) even if x or y is nil. Otherwise, no such method exists and +// evaluation proceeds to the next rule. // -// • Lastly, try to compare x and y based on their basic kinds. -// Simple kinds like booleans, integers, floats, complex numbers, strings, and -// channels are compared using the equivalent of the == operator in Go. -// Functions are only equal if they are both nil, otherwise they are unequal. +// - Lastly, try to compare x and y based on their basic kinds. +// Simple kinds like booleans, integers, floats, complex numbers, strings, +// and channels are compared using the equivalent of the == operator in Go. +// Functions are only equal if they are both nil, otherwise they are unequal. // // Structs are equal if recursively calling Equal on all fields report equal. -// If a struct contains unexported fields, Equal panics unless an Ignore option -// (e.g., cmpopts.IgnoreUnexported) ignores that field or the Exporter option -// explicitly permits comparing the unexported field. +// If a struct contains unexported fields, Equal panics unless an [Ignore] option +// (e.g., [github.com/google/go-cmp/cmp/cmpopts.IgnoreUnexported]) ignores that field +// or the [Exporter] option explicitly permits comparing the unexported field. // // Slices are equal if they are both nil or both non-nil, where recursively // calling Equal on all non-ignored slice or array elements report equal. // Empty non-nil slices and nil slices are not equal; to equate empty slices, -// consider using cmpopts.EquateEmpty. +// consider using [github.com/google/go-cmp/cmp/cmpopts.EquateEmpty]. // // Maps are equal if they are both nil or both non-nil, where recursively // calling Equal on all non-ignored map entries report equal. // Map keys are equal according to the == operator. -// To use custom comparisons for map keys, consider using cmpopts.SortMaps. +// To use custom comparisons for map keys, consider using +// [github.com/google/go-cmp/cmp/cmpopts.SortMaps]. // Empty non-nil maps and nil maps are not equal; to equate empty maps, -// consider using cmpopts.EquateEmpty. +// consider using [github.com/google/go-cmp/cmp/cmpopts.EquateEmpty]. // // Pointers and interfaces are equal if they are both nil or both non-nil, // where they have the same underlying concrete type and recursively @@ -143,7 +146,7 @@ func rootStep(x, y interface{}) PathStep { // so that they have the same parent type. var t reflect.Type if !vx.IsValid() || !vy.IsValid() || vx.Type() != vy.Type() { - t = reflect.TypeOf((*interface{})(nil)).Elem() + t = anyType if vx.IsValid() { vvx := reflect.New(t).Elem() vvx.Set(vx) @@ -319,7 +322,6 @@ func (s *state) tryMethod(t reflect.Type, vx, vy reflect.Value) bool { } func (s *state) callTRFunc(f, v reflect.Value, step Transform) reflect.Value { - v = sanitizeValue(v, f.Type().In(0)) if !s.dynChecker.Next() { return f.Call([]reflect.Value{v})[0] } @@ -343,8 +345,6 @@ func (s *state) callTRFunc(f, v reflect.Value, step Transform) reflect.Value { } func (s *state) callTTBFunc(f, x, y reflect.Value) bool { - x = sanitizeValue(x, f.Type().In(0)) - y = sanitizeValue(y, f.Type().In(1)) if !s.dynChecker.Next() { return f.Call([]reflect.Value{x, y})[0].Bool() } @@ -372,19 +372,6 @@ func detectRaces(c chan<- reflect.Value, f reflect.Value, vs ...reflect.Value) { ret = f.Call(vs)[0] } -// sanitizeValue converts nil interfaces of type T to those of type R, -// assuming that T is assignable to R. -// Otherwise, it returns the input value as is. -func sanitizeValue(v reflect.Value, t reflect.Type) reflect.Value { - // TODO(≥go1.10): Workaround for reflect bug (https://golang.org/issue/22143). - if !flags.AtLeastGo110 { - if v.Kind() == reflect.Interface && v.IsNil() && v.Type() != t { - return reflect.New(t).Elem() - } - } - return v -} - func (s *state) compareStruct(t reflect.Type, vx, vy reflect.Value) { var addr bool var vax, vay reflect.Value // Addressable versions of vx and vy @@ -654,7 +641,9 @@ type dynChecker struct{ curr, next int } // Next increments the state and reports whether a check should be performed. // // Checks occur every Nth function call, where N is a triangular number: +// // 0 1 3 6 10 15 21 28 36 45 55 66 78 91 105 120 136 153 171 190 ... +// // See https://en.wikipedia.org/wiki/Triangular_number // // This sequence ensures that the cost of checks drops significantly as diff --git a/vendor/github.com/google/go-cmp/cmp/export_unsafe.go b/vendor/github.com/google/go-cmp/cmp/export.go similarity index 95% rename from vendor/github.com/google/go-cmp/cmp/export_unsafe.go rename to vendor/github.com/google/go-cmp/cmp/export.go index 21eb548..29f82fe 100644 --- a/vendor/github.com/google/go-cmp/cmp/export_unsafe.go +++ b/vendor/github.com/google/go-cmp/cmp/export.go @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !purego - package cmp import ( @@ -11,8 +9,6 @@ import ( "unsafe" ) -const supportExporters = true - // retrieveUnexportedField uses unsafe to forcibly retrieve any field from // a struct such that the value has read-write permissions. // diff --git a/vendor/github.com/google/go-cmp/cmp/export_panic.go b/vendor/github.com/google/go-cmp/cmp/export_panic.go deleted file mode 100644 index 5ff0b42..0000000 --- a/vendor/github.com/google/go-cmp/cmp/export_panic.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2017, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build purego - -package cmp - -import "reflect" - -const supportExporters = false - -func retrieveUnexportedField(reflect.Value, reflect.StructField, bool) reflect.Value { - panic("no support for forcibly accessing unexported fields") -} diff --git a/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go b/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go index 1daaaac..36062a6 100644 --- a/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go +++ b/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !cmp_debug // +build !cmp_debug package diff diff --git a/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go b/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go index 4b91dbc..a3b97a1 100644 --- a/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go +++ b/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build cmp_debug // +build cmp_debug package diff diff --git a/vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go b/vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go index bc196b1..a248e54 100644 --- a/vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go +++ b/vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go @@ -127,9 +127,9 @@ var randBool = rand.New(rand.NewSource(time.Now().Unix())).Intn(2) == 0 // This function returns an edit-script, which is a sequence of operations // needed to convert one list into the other. The following invariants for // the edit-script are maintained: -// • eq == (es.Dist()==0) -// • nx == es.LenX() -// • ny == es.LenY() +// - eq == (es.Dist()==0) +// - nx == es.LenX() +// - ny == es.LenY() // // This algorithm is not guaranteed to be an optimal solution (i.e., one that // produces an edit-script with a minimal Levenshtein distance). This algorithm @@ -169,12 +169,13 @@ func Difference(nx, ny int, f EqualFunc) (es EditScript) { // A diagonal edge is equivalent to a matching symbol between both X and Y. // Invariants: - // • 0 ≤ fwdPath.X ≤ (fwdFrontier.X, revFrontier.X) ≤ revPath.X ≤ nx - // • 0 ≤ fwdPath.Y ≤ (fwdFrontier.Y, revFrontier.Y) ≤ revPath.Y ≤ ny + // - 0 ≤ fwdPath.X ≤ (fwdFrontier.X, revFrontier.X) ≤ revPath.X ≤ nx + // - 0 ≤ fwdPath.Y ≤ (fwdFrontier.Y, revFrontier.Y) ≤ revPath.Y ≤ ny // // In general: - // • fwdFrontier.X < revFrontier.X - // • fwdFrontier.Y < revFrontier.Y + // - fwdFrontier.X < revFrontier.X + // - fwdFrontier.Y < revFrontier.Y + // // Unless, it is time for the algorithm to terminate. fwdPath := path{+1, point{0, 0}, make(EditScript, 0, (nx+ny)/2)} revPath := path{-1, point{nx, ny}, make(EditScript, 0)} @@ -195,19 +196,21 @@ func Difference(nx, ny int, f EqualFunc) (es EditScript) { // computing sub-optimal edit-scripts between two lists. // // The algorithm is approximately as follows: - // • Searching for differences switches back-and-forth between - // a search that starts at the beginning (the top-left corner), and - // a search that starts at the end (the bottom-right corner). The goal of - // the search is connect with the search from the opposite corner. - // • As we search, we build a path in a greedy manner, where the first - // match seen is added to the path (this is sub-optimal, but provides a - // decent result in practice). When matches are found, we try the next pair - // of symbols in the lists and follow all matches as far as possible. - // • When searching for matches, we search along a diagonal going through - // through the "frontier" point. If no matches are found, we advance the - // frontier towards the opposite corner. - // • This algorithm terminates when either the X coordinates or the - // Y coordinates of the forward and reverse frontier points ever intersect. + // - Searching for differences switches back-and-forth between + // a search that starts at the beginning (the top-left corner), and + // a search that starts at the end (the bottom-right corner). + // The goal of the search is connect with the search + // from the opposite corner. + // - As we search, we build a path in a greedy manner, + // where the first match seen is added to the path (this is sub-optimal, + // but provides a decent result in practice). When matches are found, + // we try the next pair of symbols in the lists and follow all matches + // as far as possible. + // - When searching for matches, we search along a diagonal going through + // through the "frontier" point. If no matches are found, + // we advance the frontier towards the opposite corner. + // - This algorithm terminates when either the X coordinates or the + // Y coordinates of the forward and reverse frontier points ever intersect. // This algorithm is correct even if searching only in the forward direction // or in the reverse direction. We do both because it is commonly observed @@ -389,6 +392,7 @@ type point struct{ X, Y int } func (p *point) add(dx, dy int) { p.X += dx; p.Y += dy } // zigzag maps a consecutive sequence of integers to a zig-zag sequence. +// // [0 1 2 3 4 5 ...] => [0 -1 +1 -2 +2 ...] func zigzag(x int) int { if x&1 != 0 { diff --git a/vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_legacy.go b/vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_legacy.go deleted file mode 100644 index 82d1d7f..0000000 --- a/vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_legacy.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2019, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !go1.10 - -package flags - -// AtLeastGo110 reports whether the Go toolchain is at least Go 1.10. -const AtLeastGo110 = false diff --git a/vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_recent.go b/vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_recent.go deleted file mode 100644 index 8646f05..0000000 --- a/vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_recent.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2019, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build go1.10 - -package flags - -// AtLeastGo110 reports whether the Go toolchain is at least Go 1.10. -const AtLeastGo110 = true diff --git a/vendor/github.com/google/go-cmp/cmp/internal/value/name.go b/vendor/github.com/google/go-cmp/cmp/internal/value/name.go index b6c12ce..7b498bb 100644 --- a/vendor/github.com/google/go-cmp/cmp/internal/value/name.go +++ b/vendor/github.com/google/go-cmp/cmp/internal/value/name.go @@ -9,6 +9,8 @@ import ( "strconv" ) +var anyType = reflect.TypeOf((*interface{})(nil)).Elem() + // TypeString is nearly identical to reflect.Type.String, // but has an additional option to specify that full type names be used. func TypeString(t reflect.Type, qualified bool) string { @@ -20,6 +22,11 @@ func appendTypeName(b []byte, t reflect.Type, qualified, elideFunc bool) []byte // of the same name and within the same package, // but declared within the namespace of different functions. + // Use the "any" alias instead of "interface{}" for better readability. + if t == anyType { + return append(b, "any"...) + } + // Named type. if t.Name() != "" { if qualified && t.PkgPath() != "" { diff --git a/vendor/github.com/google/go-cmp/cmp/internal/value/pointer_unsafe.go b/vendor/github.com/google/go-cmp/cmp/internal/value/pointer.go similarity index 97% rename from vendor/github.com/google/go-cmp/cmp/internal/value/pointer_unsafe.go rename to vendor/github.com/google/go-cmp/cmp/internal/value/pointer.go index a605953..e5dfff6 100644 --- a/vendor/github.com/google/go-cmp/cmp/internal/value/pointer_unsafe.go +++ b/vendor/github.com/google/go-cmp/cmp/internal/value/pointer.go @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !purego - package value import ( diff --git a/vendor/github.com/google/go-cmp/cmp/internal/value/pointer_purego.go b/vendor/github.com/google/go-cmp/cmp/internal/value/pointer_purego.go deleted file mode 100644 index 44f4a5a..0000000 --- a/vendor/github.com/google/go-cmp/cmp/internal/value/pointer_purego.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2018, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build purego - -package value - -import "reflect" - -// Pointer is an opaque typed pointer and is guaranteed to be comparable. -type Pointer struct { - p uintptr - t reflect.Type -} - -// PointerOf returns a Pointer from v, which must be a -// reflect.Ptr, reflect.Slice, or reflect.Map. -func PointerOf(v reflect.Value) Pointer { - // NOTE: Storing a pointer as an uintptr is technically incorrect as it - // assumes that the GC implementation does not use a moving collector. - return Pointer{v.Pointer(), v.Type()} -} - -// IsNil reports whether the pointer is nil. -func (p Pointer) IsNil() bool { - return p.p == 0 -} - -// Uintptr returns the pointer as a uintptr. -func (p Pointer) Uintptr() uintptr { - return p.p -} diff --git a/vendor/github.com/google/go-cmp/cmp/internal/value/zero.go b/vendor/github.com/google/go-cmp/cmp/internal/value/zero.go deleted file mode 100644 index 9147a29..0000000 --- a/vendor/github.com/google/go-cmp/cmp/internal/value/zero.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2017, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package value - -import ( - "math" - "reflect" -) - -// IsZero reports whether v is the zero value. -// This does not rely on Interface and so can be used on unexported fields. -func IsZero(v reflect.Value) bool { - switch v.Kind() { - case reflect.Bool: - return v.Bool() == false - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return v.Int() == 0 - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return v.Uint() == 0 - case reflect.Float32, reflect.Float64: - return math.Float64bits(v.Float()) == 0 - case reflect.Complex64, reflect.Complex128: - return math.Float64bits(real(v.Complex())) == 0 && math.Float64bits(imag(v.Complex())) == 0 - case reflect.String: - return v.String() == "" - case reflect.UnsafePointer: - return v.Pointer() == 0 - case reflect.Chan, reflect.Func, reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice: - return v.IsNil() - case reflect.Array: - for i := 0; i < v.Len(); i++ { - if !IsZero(v.Index(i)) { - return false - } - } - return true - case reflect.Struct: - for i := 0; i < v.NumField(); i++ { - if !IsZero(v.Field(i)) { - return false - } - } - return true - } - return false -} diff --git a/vendor/github.com/google/go-cmp/cmp/options.go b/vendor/github.com/google/go-cmp/cmp/options.go index e57b9eb..754496f 100644 --- a/vendor/github.com/google/go-cmp/cmp/options.go +++ b/vendor/github.com/google/go-cmp/cmp/options.go @@ -13,15 +13,15 @@ import ( "github.com/google/go-cmp/cmp/internal/function" ) -// Option configures for specific behavior of Equal and Diff. In particular, -// the fundamental Option functions (Ignore, Transformer, and Comparer), +// Option configures for specific behavior of [Equal] and [Diff]. In particular, +// the fundamental Option functions ([Ignore], [Transformer], and [Comparer]), // configure how equality is determined. // -// The fundamental options may be composed with filters (FilterPath and -// FilterValues) to control the scope over which they are applied. +// The fundamental options may be composed with filters ([FilterPath] and +// [FilterValues]) to control the scope over which they are applied. // -// The cmp/cmpopts package provides helper functions for creating options that -// may be used with Equal and Diff. +// The [github.com/google/go-cmp/cmp/cmpopts] package provides helper functions +// for creating options that may be used with [Equal] and [Diff]. type Option interface { // filter applies all filters and returns the option that remains. // Each option may only read s.curPath and call s.callTTBFunc. @@ -33,6 +33,7 @@ type Option interface { } // applicableOption represents the following types: +// // Fundamental: ignore | validator | *comparer | *transformer // Grouping: Options type applicableOption interface { @@ -43,6 +44,7 @@ type applicableOption interface { } // coreOption represents the following types: +// // Fundamental: ignore | validator | *comparer | *transformer // Filters: *pathFilter | *valuesFilter type coreOption interface { @@ -54,9 +56,9 @@ type core struct{} func (core) isCore() {} -// Options is a list of Option values that also satisfies the Option interface. +// Options is a list of [Option] values that also satisfies the [Option] interface. // Helper comparison packages may return an Options value when packing multiple -// Option values into a single Option. When this package processes an Options, +// [Option] values into a single [Option]. When this package processes an Options, // it will be implicitly expanded into a flat list. // // Applying a filter on an Options is equivalent to applying that same filter @@ -103,16 +105,16 @@ func (opts Options) String() string { return fmt.Sprintf("Options{%s}", strings.Join(ss, ", ")) } -// FilterPath returns a new Option where opt is only evaluated if filter f -// returns true for the current Path in the value tree. +// FilterPath returns a new [Option] where opt is only evaluated if filter f +// returns true for the current [Path] in the value tree. // // This filter is called even if a slice element or map entry is missing and // provides an opportunity to ignore such cases. The filter function must be // symmetric such that the filter result is identical regardless of whether the // missing value is from x or y. // -// The option passed in may be an Ignore, Transformer, Comparer, Options, or -// a previously filtered Option. +// The option passed in may be an [Ignore], [Transformer], [Comparer], [Options], or +// a previously filtered [Option]. func FilterPath(f func(Path) bool, opt Option) Option { if f == nil { panic("invalid path filter function") @@ -140,7 +142,7 @@ func (f pathFilter) String() string { return fmt.Sprintf("FilterPath(%s, %v)", function.NameOf(reflect.ValueOf(f.fnc)), f.opt) } -// FilterValues returns a new Option where opt is only evaluated if filter f, +// FilterValues returns a new [Option] where opt is only evaluated if filter f, // which is a function of the form "func(T, T) bool", returns true for the // current pair of values being compared. If either value is invalid or // the type of the values is not assignable to T, then this filter implicitly @@ -152,8 +154,8 @@ func (f pathFilter) String() string { // If T is an interface, it is possible that f is called with two values with // different concrete types that both implement T. // -// The option passed in may be an Ignore, Transformer, Comparer, Options, or -// a previously filtered Option. +// The option passed in may be an [Ignore], [Transformer], [Comparer], [Options], or +// a previously filtered [Option]. func FilterValues(f interface{}, opt Option) Option { v := reflect.ValueOf(f) if !function.IsType(v.Type(), function.ValueFilter) || v.IsNil() { @@ -190,9 +192,9 @@ func (f valuesFilter) String() string { return fmt.Sprintf("FilterValues(%s, %v)", function.NameOf(f.fnc), f.opt) } -// Ignore is an Option that causes all comparisons to be ignored. -// This value is intended to be combined with FilterPath or FilterValues. -// It is an error to pass an unfiltered Ignore option to Equal. +// Ignore is an [Option] that causes all comparisons to be ignored. +// This value is intended to be combined with [FilterPath] or [FilterValues]. +// It is an error to pass an unfiltered Ignore option to [Equal]. func Ignore() Option { return ignore{} } type ignore struct{ core } @@ -232,6 +234,8 @@ func (validator) apply(s *state, vx, vy reflect.Value) { name = fmt.Sprintf("%q.%v", t.PkgPath(), t.Name()) // e.g., "path/to/package".MyType if _, ok := reflect.New(t).Interface().(error); ok { help = "consider using cmpopts.EquateErrors to compare error values" + } else if t.Comparable() { + help = "consider using cmpopts.EquateComparable to compare comparable Go types" } } else { // Unnamed type with unexported fields. Derive PkgPath from field. @@ -252,7 +256,7 @@ const identRx = `[_\p{L}][_\p{L}\p{N}]*` var identsRx = regexp.MustCompile(`^` + identRx + `(\.` + identRx + `)*$`) -// Transformer returns an Option that applies a transformation function that +// Transformer returns an [Option] that applies a transformation function that // converts values of a certain type into that of another. // // The transformer f must be a function "func(T) R" that converts values of @@ -263,13 +267,14 @@ var identsRx = regexp.MustCompile(`^` + identRx + `(\.` + identRx + `)*$`) // same transform to the output of itself (e.g., in the case where the // input and output types are the same), an implicit filter is added such that // a transformer is applicable only if that exact transformer is not already -// in the tail of the Path since the last non-Transform step. +// in the tail of the [Path] since the last non-[Transform] step. // For situations where the implicit filter is still insufficient, -// consider using cmpopts.AcyclicTransformer, which adds a filter -// to prevent the transformer from being recursively applied upon itself. +// consider using [github.com/google/go-cmp/cmp/cmpopts.AcyclicTransformer], +// which adds a filter to prevent the transformer from +// being recursively applied upon itself. // -// The name is a user provided label that is used as the Transform.Name in the -// transformation PathStep (and eventually shown in the Diff output). +// The name is a user provided label that is used as the [Transform.Name] in the +// transformation [PathStep] (and eventually shown in the [Diff] output). // The name must be a valid identifier or qualified identifier in Go syntax. // If empty, an arbitrary name is used. func Transformer(name string, f interface{}) Option { @@ -327,7 +332,7 @@ func (tr transformer) String() string { return fmt.Sprintf("Transformer(%s, %s)", tr.name, function.NameOf(tr.fnc)) } -// Comparer returns an Option that determines whether two values are equal +// Comparer returns an [Option] that determines whether two values are equal // to each other. // // The comparer f must be a function "func(T, T) bool" and is implicitly @@ -336,9 +341,9 @@ func (tr transformer) String() string { // both implement T. // // The equality function must be: -// • Symmetric: equal(x, y) == equal(y, x) -// • Deterministic: equal(x, y) == equal(x, y) -// • Pure: equal(x, y) does not modify x or y +// - Symmetric: equal(x, y) == equal(y, x) +// - Deterministic: equal(x, y) == equal(x, y) +// - Pure: equal(x, y) does not modify x or y func Comparer(f interface{}) Option { v := reflect.ValueOf(f) if !function.IsType(v.Type(), function.Equal) || v.IsNil() { @@ -375,35 +380,32 @@ func (cm comparer) String() string { return fmt.Sprintf("Comparer(%s)", function.NameOf(cm.fnc)) } -// Exporter returns an Option that specifies whether Equal is allowed to +// Exporter returns an [Option] that specifies whether [Equal] is allowed to // introspect into the unexported fields of certain struct types. // // Users of this option must understand that comparing on unexported fields // from external packages is not safe since changes in the internal -// implementation of some external package may cause the result of Equal +// implementation of some external package may cause the result of [Equal] // to unexpectedly change. However, it may be valid to use this option on types // defined in an internal package where the semantic meaning of an unexported // field is in the control of the user. // -// In many cases, a custom Comparer should be used instead that defines +// In many cases, a custom [Comparer] should be used instead that defines // equality as a function of the public API of a type rather than the underlying // unexported implementation. // -// For example, the reflect.Type documentation defines equality to be determined +// For example, the [reflect.Type] documentation defines equality to be determined // by the == operator on the interface (essentially performing a shallow pointer -// comparison) and most attempts to compare *regexp.Regexp types are interested +// comparison) and most attempts to compare *[regexp.Regexp] types are interested // in only checking that the regular expression strings are equal. -// Both of these are accomplished using Comparers: +// Both of these are accomplished using [Comparer] options: // // Comparer(func(x, y reflect.Type) bool { return x == y }) // Comparer(func(x, y *regexp.Regexp) bool { return x.String() == y.String() }) // -// In other cases, the cmpopts.IgnoreUnexported option can be used to ignore -// all unexported fields on specified struct types. +// In other cases, the [github.com/google/go-cmp/cmp/cmpopts.IgnoreUnexported] +// option can be used to ignore all unexported fields on specified struct types. func Exporter(f func(reflect.Type) bool) Option { - if !supportExporters { - panic("Exporter is not supported on purego builds") - } return exporter(f) } @@ -413,10 +415,10 @@ func (exporter) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableO panic("not implemented") } -// AllowUnexported returns an Options that allows Equal to forcibly introspect +// AllowUnexported returns an [Option] that allows [Equal] to forcibly introspect // unexported fields of the specified struct types. // -// See Exporter for the proper use of this option. +// See [Exporter] for the proper use of this option. func AllowUnexported(types ...interface{}) Option { m := make(map[reflect.Type]bool) for _, typ := range types { @@ -430,7 +432,7 @@ func AllowUnexported(types ...interface{}) Option { } // Result represents the comparison result for a single node and -// is provided by cmp when calling Result (see Reporter). +// is provided by cmp when calling Report (see [Reporter]). type Result struct { _ [0]func() // Make Result incomparable flags resultFlags @@ -443,7 +445,7 @@ func (r Result) Equal() bool { } // ByIgnore reports whether the node is equal because it was ignored. -// This never reports true if Equal reports false. +// This never reports true if [Result.Equal] reports false. func (r Result) ByIgnore() bool { return r.flags&reportByIgnore != 0 } @@ -453,7 +455,7 @@ func (r Result) ByMethod() bool { return r.flags&reportByMethod != 0 } -// ByFunc reports whether a Comparer function determined equality. +// ByFunc reports whether a [Comparer] function determined equality. func (r Result) ByFunc() bool { return r.flags&reportByFunc != 0 } @@ -476,7 +478,7 @@ const ( reportByCycle ) -// Reporter is an Option that can be passed to Equal. When Equal traverses +// Reporter is an [Option] that can be passed to [Equal]. When [Equal] traverses // the value trees, it calls PushStep as it descends into each node in the // tree and PopStep as it ascend out of the node. The leaves of the tree are // either compared (determined to be equal or not equal) or ignored and reported diff --git a/vendor/github.com/google/go-cmp/cmp/path.go b/vendor/github.com/google/go-cmp/cmp/path.go index 3d45c1a..c3c1456 100644 --- a/vendor/github.com/google/go-cmp/cmp/path.go +++ b/vendor/github.com/google/go-cmp/cmp/path.go @@ -14,9 +14,9 @@ import ( "github.com/google/go-cmp/cmp/internal/value" ) -// Path is a list of PathSteps describing the sequence of operations to get +// Path is a list of [PathStep] describing the sequence of operations to get // from some root type to the current position in the value tree. -// The first Path element is always an operation-less PathStep that exists +// The first Path element is always an operation-less [PathStep] that exists // simply to identify the initial type. // // When traversing structs with embedded structs, the embedded struct will @@ -29,8 +29,13 @@ type Path []PathStep // a value's tree structure. Users of this package never need to implement // these types as values of this type will be returned by this package. // -// Implementations of this interface are -// StructField, SliceIndex, MapIndex, Indirect, TypeAssertion, and Transform. +// Implementations of this interface: +// - [StructField] +// - [SliceIndex] +// - [MapIndex] +// - [Indirect] +// - [TypeAssertion] +// - [Transform] type PathStep interface { String() string @@ -41,13 +46,13 @@ type PathStep interface { // The type of each valid value is guaranteed to be identical to Type. // // In some cases, one or both may be invalid or have restrictions: - // • For StructField, both are not interface-able if the current field - // is unexported and the struct type is not explicitly permitted by - // an Exporter to traverse unexported fields. - // • For SliceIndex, one may be invalid if an element is missing from - // either the x or y slice. - // • For MapIndex, one may be invalid if an entry is missing from - // either the x or y map. + // - For StructField, both are not interface-able if the current field + // is unexported and the struct type is not explicitly permitted by + // an Exporter to traverse unexported fields. + // - For SliceIndex, one may be invalid if an element is missing from + // either the x or y slice. + // - For MapIndex, one may be invalid if an entry is missing from + // either the x or y map. // // The provided values must not be mutated. Values() (vx, vy reflect.Value) @@ -70,8 +75,9 @@ func (pa *Path) pop() { *pa = (*pa)[:len(*pa)-1] } -// Last returns the last PathStep in the Path. -// If the path is empty, this returns a non-nil PathStep that reports a nil Type. +// Last returns the last [PathStep] in the Path. +// If the path is empty, this returns a non-nil [PathStep] +// that reports a nil [PathStep.Type]. func (pa Path) Last() PathStep { return pa.Index(-1) } @@ -79,7 +85,8 @@ func (pa Path) Last() PathStep { // Index returns the ith step in the Path and supports negative indexing. // A negative index starts counting from the tail of the Path such that -1 // refers to the last step, -2 refers to the second-to-last step, and so on. -// If index is invalid, this returns a non-nil PathStep that reports a nil Type. +// If index is invalid, this returns a non-nil [PathStep] +// that reports a nil [PathStep.Type]. func (pa Path) Index(i int) PathStep { if i < 0 { i = len(pa) + i @@ -94,6 +101,7 @@ func (pa Path) Index(i int) PathStep { // The simplified path only contains struct field accesses. // // For example: +// // MyMap.MySlices.MyField func (pa Path) String() string { var ss []string @@ -108,6 +116,7 @@ func (pa Path) String() string { // GoString returns the path to a specific node using Go syntax. // // For example: +// // (*root.MyMap["key"].(*mypkg.MyStruct).MySlices)[2][3].MyField func (pa Path) GoString() string { var ssPre, ssPost []string @@ -159,14 +168,15 @@ func (ps pathStep) String() string { if ps.typ == nil { return "" } - s := ps.typ.String() + s := value.TypeString(ps.typ, false) if s == "" || strings.ContainsAny(s, "{}\n") { return "root" // Type too simple or complex to print } return fmt.Sprintf("{%s}", s) } -// StructField represents a struct field access on a field called Name. +// StructField is a [PathStep] that represents a struct field access +// on a field called [StructField.Name]. type StructField struct{ *structField } type structField struct { pathStep @@ -178,7 +188,7 @@ type structField struct { unexported bool mayForce bool // Forcibly allow visibility paddr bool // Was parent addressable? - pvx, pvy reflect.Value // Parent values (always addressible) + pvx, pvy reflect.Value // Parent values (always addressable) field reflect.StructField // Field information } @@ -202,10 +212,11 @@ func (sf StructField) String() string { return fmt.Sprintf(".%s", sf.name) } func (sf StructField) Name() string { return sf.name } // Index is the index of the field in the parent struct type. -// See reflect.Type.Field. +// See [reflect.Type.Field]. func (sf StructField) Index() int { return sf.idx } -// SliceIndex is an index operation on a slice or array at some index Key. +// SliceIndex is a [PathStep] that represents an index operation on +// a slice or array at some index [SliceIndex.Key]. type SliceIndex struct{ *sliceIndex } type sliceIndex struct { pathStep @@ -245,12 +256,12 @@ func (si SliceIndex) Key() int { // all of the indexes to be shifted. If an index is -1, then that // indicates that the element does not exist in the associated slice. // -// Key is guaranteed to return -1 if and only if the indexes returned -// by SplitKeys are not the same. SplitKeys will never return -1 for +// [SliceIndex.Key] is guaranteed to return -1 if and only if the indexes +// returned by SplitKeys are not the same. SplitKeys will never return -1 for // both indexes. func (si SliceIndex) SplitKeys() (ix, iy int) { return si.xkey, si.ykey } -// MapIndex is an index operation on a map at some index Key. +// MapIndex is a [PathStep] that represents an index operation on a map at some index Key. type MapIndex struct{ *mapIndex } type mapIndex struct { pathStep @@ -264,7 +275,7 @@ func (mi MapIndex) String() string { return fmt.Sprintf("[%#v]", // Key is the value of the map key. func (mi MapIndex) Key() reflect.Value { return mi.key } -// Indirect represents pointer indirection on the parent type. +// Indirect is a [PathStep] that represents pointer indirection on the parent type. type Indirect struct{ *indirect } type indirect struct { pathStep @@ -274,7 +285,7 @@ func (in Indirect) Type() reflect.Type { return in.typ } func (in Indirect) Values() (vx, vy reflect.Value) { return in.vx, in.vy } func (in Indirect) String() string { return "*" } -// TypeAssertion represents a type assertion on an interface. +// TypeAssertion is a [PathStep] that represents a type assertion on an interface. type TypeAssertion struct{ *typeAssertion } type typeAssertion struct { pathStep @@ -282,9 +293,10 @@ type typeAssertion struct { func (ta TypeAssertion) Type() reflect.Type { return ta.typ } func (ta TypeAssertion) Values() (vx, vy reflect.Value) { return ta.vx, ta.vy } -func (ta TypeAssertion) String() string { return fmt.Sprintf(".(%v)", ta.typ) } +func (ta TypeAssertion) String() string { return fmt.Sprintf(".(%v)", value.TypeString(ta.typ, false)) } -// Transform is a transformation from the parent type to the current type. +// Transform is a [PathStep] that represents a transformation +// from the parent type to the current type. type Transform struct{ *transform } type transform struct { pathStep @@ -295,13 +307,13 @@ func (tf Transform) Type() reflect.Type { return tf.typ } func (tf Transform) Values() (vx, vy reflect.Value) { return tf.vx, tf.vy } func (tf Transform) String() string { return fmt.Sprintf("%s()", tf.trans.name) } -// Name is the name of the Transformer. +// Name is the name of the [Transformer]. func (tf Transform) Name() string { return tf.trans.name } // Func is the function pointer to the transformer function. func (tf Transform) Func() reflect.Value { return tf.trans.fnc } -// Option returns the originally constructed Transformer option. +// Option returns the originally constructed [Transformer] option. // The == operator can be used to detect the exact option used. func (tf Transform) Option() Option { return tf.trans } @@ -315,7 +327,7 @@ func (tf Transform) Option() Option { return tf.trans } // pops the address from the stack. Thus, when traversing into a pointer from // reflect.Ptr, reflect.Slice element, or reflect.Map, we can detect cycles // by checking whether the pointer has already been visited. The cycle detection -// uses a seperate stack for the x and y values. +// uses a separate stack for the x and y values. // // If a cycle is detected we need to determine whether the two pointers // should be considered equal. The definition of equality chosen by Equal diff --git a/vendor/github.com/google/go-cmp/cmp/report_compare.go b/vendor/github.com/google/go-cmp/cmp/report_compare.go index a6c070c..2050bf6 100644 --- a/vendor/github.com/google/go-cmp/cmp/report_compare.go +++ b/vendor/github.com/google/go-cmp/cmp/report_compare.go @@ -7,8 +7,6 @@ package cmp import ( "fmt" "reflect" - - "github.com/google/go-cmp/cmp/internal/value" ) // numContextRecords is the number of surrounding equal records to print. @@ -79,7 +77,7 @@ func (opts formatOptions) verbosity() uint { } } -const maxVerbosityPreset = 3 +const maxVerbosityPreset = 6 // verbosityPreset modifies the verbosity settings given an index // between 0 and maxVerbosityPreset, inclusive. @@ -100,7 +98,7 @@ func verbosityPreset(opts formatOptions, i int) formatOptions { func (opts formatOptions) FormatDiff(v *valueNode, ptrs *pointerReferences) (out textNode) { if opts.DiffMode == diffIdentical { opts = opts.WithVerbosity(1) - } else { + } else if opts.verbosity() < 3 { opts = opts.WithVerbosity(3) } @@ -116,7 +114,10 @@ func (opts formatOptions) FormatDiff(v *valueNode, ptrs *pointerReferences) (out } // For leaf nodes, format the value based on the reflect.Values alone. - if v.MaxDepth == 0 { + // As a special case, treat equal []byte as a leaf nodes. + isBytes := v.Type.Kind() == reflect.Slice && v.Type.Elem() == byteType + isEqualBytes := isBytes && v.NumDiff+v.NumIgnored+v.NumTransformed == 0 + if v.MaxDepth == 0 || isEqualBytes { switch opts.DiffMode { case diffUnknown, diffIdentical: // Format Equal. @@ -245,11 +246,11 @@ func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind, pt var isZero bool switch opts.DiffMode { case diffIdentical: - isZero = value.IsZero(r.Value.ValueX) || value.IsZero(r.Value.ValueY) + isZero = r.Value.ValueX.IsZero() || r.Value.ValueY.IsZero() case diffRemoved: - isZero = value.IsZero(r.Value.ValueX) + isZero = r.Value.ValueX.IsZero() case diffInserted: - isZero = value.IsZero(r.Value.ValueY) + isZero = r.Value.ValueY.IsZero() } if isZero { continue diff --git a/vendor/github.com/google/go-cmp/cmp/report_reflect.go b/vendor/github.com/google/go-cmp/cmp/report_reflect.go index 33f0357..e39f422 100644 --- a/vendor/github.com/google/go-cmp/cmp/report_reflect.go +++ b/vendor/github.com/google/go-cmp/cmp/report_reflect.go @@ -16,6 +16,13 @@ import ( "github.com/google/go-cmp/cmp/internal/value" ) +var ( + anyType = reflect.TypeOf((*interface{})(nil)).Elem() + stringType = reflect.TypeOf((*string)(nil)).Elem() + bytesType = reflect.TypeOf((*[]byte)(nil)).Elem() + byteType = reflect.TypeOf((*byte)(nil)).Elem() +) + type formatValueOptions struct { // AvoidStringer controls whether to avoid calling custom stringer // methods like error.Error or fmt.Stringer.String. @@ -184,7 +191,7 @@ func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind, } for i := 0; i < v.NumField(); i++ { vv := v.Field(i) - if value.IsZero(vv) { + if vv.IsZero() { continue // Elide fields with zero values } if len(list) == maxLen { @@ -192,7 +199,7 @@ func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind, break } sf := t.Field(i) - if supportExporters && !isExported(sf.Name) { + if !isExported(sf.Name) { vv = retrieveUnexportedField(v, sf, true) } s := opts.WithTypeMode(autoType).FormatValue(vv, t.Kind(), ptrs) @@ -205,12 +212,13 @@ func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind, } // Check whether this is a []byte of text data. - if t.Elem() == reflect.TypeOf(byte(0)) { + if t.Elem() == byteType { b := v.Bytes() - isPrintSpace := func(r rune) bool { return unicode.IsPrint(r) && unicode.IsSpace(r) } + isPrintSpace := func(r rune) bool { return unicode.IsPrint(r) || unicode.IsSpace(r) } if len(b) > 0 && utf8.Valid(b) && len(bytes.TrimFunc(b, isPrintSpace)) == 0 { out = opts.formatString("", string(b)) - return opts.WithTypeMode(emitType).FormatType(t, out) + skipType = true + return opts.FormatType(t, out) } } @@ -281,7 +289,12 @@ func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind, } defer ptrs.Pop() - skipType = true // Let the underlying value print the type instead + // Skip the name only if this is an unnamed pointer type. + // Otherwise taking the address of a value does not reproduce + // the named pointer type. + if v.Type().Name() == "" { + skipType = true // Let the underlying value print the type instead + } out = opts.FormatValue(v.Elem(), t.Kind(), ptrs) out = wrapTrunkReference(ptrRef, opts.PrintAddresses, out) out = &textWrap{Prefix: "&", Value: out} @@ -292,7 +305,6 @@ func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind, } // Interfaces accept different concrete types, // so configure the underlying value to explicitly print the type. - skipType = true // Print the concrete type instead return opts.WithTypeMode(emitType).FormatValue(v.Elem(), t.Kind(), ptrs) default: panic(fmt.Sprintf("%v kind not handled", v.Kind())) diff --git a/vendor/github.com/google/go-cmp/cmp/report_slices.go b/vendor/github.com/google/go-cmp/cmp/report_slices.go index da04caf..23e444f 100644 --- a/vendor/github.com/google/go-cmp/cmp/report_slices.go +++ b/vendor/github.com/google/go-cmp/cmp/report_slices.go @@ -7,6 +7,7 @@ package cmp import ( "bytes" "fmt" + "math" "reflect" "strconv" "strings" @@ -26,8 +27,6 @@ func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool { return false // No differences detected case !v.ValueX.IsValid() || !v.ValueY.IsValid(): return false // Both values must be valid - case v.Type.Kind() == reflect.Slice && (v.ValueX.Len() == 0 || v.ValueY.Len() == 0): - return false // Both slice values have to be non-empty case v.NumIgnored > 0: return false // Some ignore option was used case v.NumTransformed > 0: @@ -45,7 +44,16 @@ func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool { return false } - switch t := v.Type; t.Kind() { + // Check whether this is an interface with the same concrete types. + t := v.Type + vx, vy := v.ValueX, v.ValueY + if t.Kind() == reflect.Interface && !vx.IsNil() && !vy.IsNil() && vx.Elem().Type() == vy.Elem().Type() { + vx, vy = vx.Elem(), vy.Elem() + t = vx.Type() + } + + // Check whether we provide specialized diffing for this type. + switch t.Kind() { case reflect.String: case reflect.Array, reflect.Slice: // Only slices of primitive types have specialized handling. @@ -57,6 +65,11 @@ func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool { return false } + // Both slice values have to be non-empty. + if t.Kind() == reflect.Slice && (vx.Len() == 0 || vy.Len() == 0) { + return false + } + // If a sufficient number of elements already differ, // use specialized formatting even if length requirement is not met. if v.NumDiff > v.NumSame { @@ -67,8 +80,8 @@ func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool { } // Use specialized string diffing for longer slices or strings. - const minLength = 64 - return v.ValueX.Len() >= minLength && v.ValueY.Len() >= minLength + const minLength = 32 + return vx.Len() >= minLength && vy.Len() >= minLength } // FormatDiffSlice prints a diff for the slices (or strings) represented by v. @@ -77,17 +90,23 @@ func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool { func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { assert(opts.DiffMode == diffUnknown) t, vx, vy := v.Type, v.ValueX, v.ValueY + if t.Kind() == reflect.Interface { + vx, vy = vx.Elem(), vy.Elem() + t = vx.Type() + opts = opts.WithTypeMode(emitType) + } // Auto-detect the type of the data. - var isLinedText, isText, isBinary bool var sx, sy string + var ssx, ssy []string + var isString, isMostlyText, isPureLinedText, isBinary bool switch { case t.Kind() == reflect.String: sx, sy = vx.String(), vy.String() - isText = true // Initial estimate, verify later - case t.Kind() == reflect.Slice && t.Elem() == reflect.TypeOf(byte(0)): + isString = true + case t.Kind() == reflect.Slice && t.Elem() == byteType: sx, sy = string(vx.Bytes()), string(vy.Bytes()) - isBinary = true // Initial estimate, verify later + isString = true case t.Kind() == reflect.Array: // Arrays need to be addressable for slice operations to work. vx2, vy2 := reflect.New(t).Elem(), reflect.New(t).Elem() @@ -95,13 +114,12 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { vy2.Set(vy) vx, vy = vx2, vy2 } - if isText || isBinary { - var numLines, lastLineIdx, maxLineLen int - isBinary = !utf8.ValidString(sx) || !utf8.ValidString(sy) + if isString { + var numTotalRunes, numValidRunes, numLines, lastLineIdx, maxLineLen int for i, r := range sx + sy { - if !(unicode.IsPrint(r) || unicode.IsSpace(r)) || r == utf8.RuneError { - isBinary = true - break + numTotalRunes++ + if (unicode.IsPrint(r) || unicode.IsSpace(r)) && r != utf8.RuneError { + numValidRunes++ } if r == '\n' { if maxLineLen < i-lastLineIdx { @@ -111,8 +129,29 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { numLines++ } } - isText = !isBinary - isLinedText = isText && numLines >= 4 && maxLineLen <= 1024 + isPureText := numValidRunes == numTotalRunes + isMostlyText = float64(numValidRunes) > math.Floor(0.90*float64(numTotalRunes)) + isPureLinedText = isPureText && numLines >= 4 && maxLineLen <= 1024 + isBinary = !isMostlyText + + // Avoid diffing by lines if it produces a significantly more complex + // edit script than diffing by bytes. + if isPureLinedText { + ssx = strings.Split(sx, "\n") + ssy = strings.Split(sy, "\n") + esLines := diff.Difference(len(ssx), len(ssy), func(ix, iy int) diff.Result { + return diff.BoolResult(ssx[ix] == ssy[iy]) + }) + esBytes := diff.Difference(len(sx), len(sy), func(ix, iy int) diff.Result { + return diff.BoolResult(sx[ix] == sy[iy]) + }) + efficiencyLines := float64(esLines.Dist()) / float64(len(esLines)) + efficiencyBytes := float64(esBytes.Dist()) / float64(len(esBytes)) + quotedLength := len(strconv.Quote(sx + sy)) + unquotedLength := len(sx) + len(sy) + escapeExpansionRatio := float64(quotedLength) / float64(unquotedLength) + isPureLinedText = efficiencyLines < 4*efficiencyBytes || escapeExpansionRatio > 1.1 + } } // Format the string into printable records. @@ -121,9 +160,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { switch { // If the text appears to be multi-lined text, // then perform differencing across individual lines. - case isLinedText: - ssx := strings.Split(sx, "\n") - ssy := strings.Split(sy, "\n") + case isPureLinedText: list = opts.formatDiffSlice( reflect.ValueOf(ssx), reflect.ValueOf(ssy), 1, "line", func(v reflect.Value, d diffMode) textRecord { @@ -137,12 +174,13 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { // differences in a string literal. This format is more readable, // but has edge-cases where differences are visually indistinguishable. // This format is avoided under the following conditions: - // • A line starts with `"""` - // • A line starts with "..." - // • A line contains non-printable characters - // • Adjacent different lines differ only by whitespace + // - A line starts with `"""` + // - A line starts with "..." + // - A line contains non-printable characters + // - Adjacent different lines differ only by whitespace // // For example: + // // """ // ... // 3 identical lines // foo @@ -197,7 +235,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { var out textNode = &textWrap{Prefix: "(", Value: list2, Suffix: ")"} switch t.Kind() { case reflect.String: - if t != reflect.TypeOf(string("")) { + if t != stringType { out = opts.FormatType(t, out) } case reflect.Slice: @@ -212,7 +250,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { // If the text appears to be single-lined text, // then perform differencing in approximately fixed-sized chunks. // The output is printed as quoted strings. - case isText: + case isMostlyText: list = opts.formatDiffSlice( reflect.ValueOf(sx), reflect.ValueOf(sy), 64, "byte", func(v reflect.Value, d diffMode) textRecord { @@ -220,7 +258,6 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { return textRecord{Diff: d, Value: textLine(s)} }, ) - delim = "" // If the text appears to be binary data, // then perform differencing in approximately fixed-sized chunks. @@ -282,7 +319,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { // Wrap the output with appropriate type information. var out textNode = &textWrap{Prefix: "{", Value: list, Suffix: "}"} - if !isText { + if !isMostlyText { // The "{...}" byte-sequence literal is not valid Go syntax for strings. // Emit the type for extra clarity (e.g. "string{...}"). if t.Kind() == reflect.String { @@ -293,12 +330,12 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { switch t.Kind() { case reflect.String: out = &textWrap{Prefix: "strings.Join(", Value: out, Suffix: fmt.Sprintf(", %q)", delim)} - if t != reflect.TypeOf(string("")) { + if t != stringType { out = opts.FormatType(t, out) } case reflect.Slice: out = &textWrap{Prefix: "bytes.Join(", Value: out, Suffix: fmt.Sprintf(", %q)", delim)} - if t != reflect.TypeOf([]byte(nil)) { + if t != bytesType { out = opts.FormatType(t, out) } } @@ -321,8 +358,11 @@ func (opts formatOptions) formatDiffSlice( vx, vy reflect.Value, chunkSize int, name string, makeRec func(reflect.Value, diffMode) textRecord, ) (list textList) { - es := diff.Difference(vx.Len(), vy.Len(), func(ix int, iy int) diff.Result { - return diff.BoolResult(vx.Index(ix).Interface() == vy.Index(iy).Interface()) + eq := func(ix, iy int) bool { + return vx.Index(ix).Interface() == vy.Index(iy).Interface() + } + es := diff.Difference(vx.Len(), vy.Len(), func(ix, iy int) diff.Result { + return diff.BoolResult(eq(ix, iy)) }) appendChunks := func(v reflect.Value, d diffMode) int { @@ -347,6 +387,7 @@ func (opts formatOptions) formatDiffSlice( groups := coalesceAdjacentEdits(name, es) groups = coalesceInterveningIdentical(groups, chunkSize/4) + groups = cleanupSurroundingIdentical(groups, eq) maxGroup := diffStats{Name: name} for i, ds := range groups { if maxLen >= 0 && numDiffs >= maxLen { @@ -399,25 +440,35 @@ func (opts formatOptions) formatDiffSlice( // coalesceAdjacentEdits coalesces the list of edits into groups of adjacent // equal or unequal counts. +// +// Example: +// +// Input: "..XXY...Y" +// Output: [ +// {NumIdentical: 2}, +// {NumRemoved: 2, NumInserted 1}, +// {NumIdentical: 3}, +// {NumInserted: 1}, +// ] func coalesceAdjacentEdits(name string, es diff.EditScript) (groups []diffStats) { - var prevCase int // Arbitrary index into which case last occurred - lastStats := func(i int) *diffStats { - if prevCase != i { + var prevMode byte + lastStats := func(mode byte) *diffStats { + if prevMode != mode { groups = append(groups, diffStats{Name: name}) - prevCase = i + prevMode = mode } return &groups[len(groups)-1] } for _, e := range es { switch e { case diff.Identity: - lastStats(1).NumIdentical++ + lastStats('=').NumIdentical++ case diff.UniqueX: - lastStats(2).NumRemoved++ + lastStats('!').NumRemoved++ case diff.UniqueY: - lastStats(2).NumInserted++ + lastStats('!').NumInserted++ case diff.Modified: - lastStats(2).NumModified++ + lastStats('!').NumModified++ } } return groups @@ -427,6 +478,34 @@ func coalesceAdjacentEdits(name string, es diff.EditScript) (groups []diffStats) // equal groups into adjacent unequal groups that currently result in a // dual inserted/removed printout. This acts as a high-pass filter to smooth // out high-frequency changes within the windowSize. +// +// Example: +// +// WindowSize: 16, +// Input: [ +// {NumIdentical: 61}, // group 0 +// {NumRemoved: 3, NumInserted: 1}, // group 1 +// {NumIdentical: 6}, // ├── coalesce +// {NumInserted: 2}, // ├── coalesce +// {NumIdentical: 1}, // ├── coalesce +// {NumRemoved: 9}, // └── coalesce +// {NumIdentical: 64}, // group 2 +// {NumRemoved: 3, NumInserted: 1}, // group 3 +// {NumIdentical: 6}, // ├── coalesce +// {NumInserted: 2}, // ├── coalesce +// {NumIdentical: 1}, // ├── coalesce +// {NumRemoved: 7}, // ├── coalesce +// {NumIdentical: 1}, // ├── coalesce +// {NumRemoved: 2}, // └── coalesce +// {NumIdentical: 63}, // group 4 +// ] +// Output: [ +// {NumIdentical: 61}, +// {NumIdentical: 7, NumRemoved: 12, NumInserted: 3}, +// {NumIdentical: 64}, +// {NumIdentical: 8, NumRemoved: 12, NumInserted: 3}, +// {NumIdentical: 63}, +// ] func coalesceInterveningIdentical(groups []diffStats, windowSize int) []diffStats { groups, groupsOrig := groups[:0], groups for i, ds := range groupsOrig { @@ -446,3 +525,90 @@ func coalesceInterveningIdentical(groups []diffStats, windowSize int) []diffStat } return groups } + +// cleanupSurroundingIdentical scans through all unequal groups, and +// moves any leading sequence of equal elements to the preceding equal group and +// moves and trailing sequence of equal elements to the succeeding equal group. +// +// This is necessary since coalesceInterveningIdentical may coalesce edit groups +// together such that leading/trailing spans of equal elements becomes possible. +// Note that this can occur even with an optimal diffing algorithm. +// +// Example: +// +// Input: [ +// {NumIdentical: 61}, +// {NumIdentical: 1 , NumRemoved: 11, NumInserted: 2}, // assume 3 leading identical elements +// {NumIdentical: 67}, +// {NumIdentical: 7, NumRemoved: 12, NumInserted: 3}, // assume 10 trailing identical elements +// {NumIdentical: 54}, +// ] +// Output: [ +// {NumIdentical: 64}, // incremented by 3 +// {NumRemoved: 9}, +// {NumIdentical: 67}, +// {NumRemoved: 9}, +// {NumIdentical: 64}, // incremented by 10 +// ] +func cleanupSurroundingIdentical(groups []diffStats, eq func(i, j int) bool) []diffStats { + var ix, iy int // indexes into sequence x and y + for i, ds := range groups { + // Handle equal group. + if ds.NumDiff() == 0 { + ix += ds.NumIdentical + iy += ds.NumIdentical + continue + } + + // Handle unequal group. + nx := ds.NumIdentical + ds.NumRemoved + ds.NumModified + ny := ds.NumIdentical + ds.NumInserted + ds.NumModified + var numLeadingIdentical, numTrailingIdentical int + for j := 0; j < nx && j < ny && eq(ix+j, iy+j); j++ { + numLeadingIdentical++ + } + for j := 0; j < nx && j < ny && eq(ix+nx-1-j, iy+ny-1-j); j++ { + numTrailingIdentical++ + } + if numIdentical := numLeadingIdentical + numTrailingIdentical; numIdentical > 0 { + if numLeadingIdentical > 0 { + // Remove leading identical span from this group and + // insert it into the preceding group. + if i-1 >= 0 { + groups[i-1].NumIdentical += numLeadingIdentical + } else { + // No preceding group exists, so prepend a new group, + // but do so after we finish iterating over all groups. + defer func() { + groups = append([]diffStats{{Name: groups[0].Name, NumIdentical: numLeadingIdentical}}, groups...) + }() + } + // Increment indexes since the preceding group would have handled this. + ix += numLeadingIdentical + iy += numLeadingIdentical + } + if numTrailingIdentical > 0 { + // Remove trailing identical span from this group and + // insert it into the succeeding group. + if i+1 < len(groups) { + groups[i+1].NumIdentical += numTrailingIdentical + } else { + // No succeeding group exists, so append a new group, + // but do so after we finish iterating over all groups. + defer func() { + groups = append(groups, diffStats{Name: groups[len(groups)-1].Name, NumIdentical: numTrailingIdentical}) + }() + } + // Do not increment indexes since the succeeding group will handle this. + } + + // Update this group since some identical elements were removed. + nx -= numIdentical + ny -= numIdentical + groups[i] = diffStats{Name: ds.Name, NumRemoved: nx, NumInserted: ny} + } + ix += nx + iy += ny + } + return groups +} diff --git a/vendor/github.com/google/go-cmp/cmp/report_text.go b/vendor/github.com/google/go-cmp/cmp/report_text.go index 0fd46d7..388fcf5 100644 --- a/vendor/github.com/google/go-cmp/cmp/report_text.go +++ b/vendor/github.com/google/go-cmp/cmp/report_text.go @@ -393,6 +393,7 @@ func (s diffStats) Append(ds diffStats) diffStats { // String prints a humanly-readable summary of coalesced records. // // Example: +// // diffStats{Name: "Field", NumIgnored: 5}.String() => "5 ignored fields" func (s diffStats) String() string { var ss []string diff --git a/vendor/github.com/google/go-querystring/query/encode.go b/vendor/github.com/google/go-querystring/query/encode.go index 37080b1..91198f8 100644 --- a/vendor/github.com/google/go-querystring/query/encode.go +++ b/vendor/github.com/google/go-querystring/query/encode.go @@ -51,8 +51,8 @@ type Encoder interface { // - the field is empty and its tag specifies the "omitempty" option // // The empty values are false, 0, any nil pointer or interface value, any array -// slice, map, or string of length zero, and any time.Time that returns true -// for IsZero(). +// slice, map, or string of length zero, and any type (such as time.Time) that +// returns true for IsZero(). // // The URL parameter name defaults to the struct field name but can be // specified in the struct field's tag value. The "url" key in the struct @@ -82,7 +82,14 @@ type Encoder interface { // // time.Time values default to encoding as RFC3339 timestamps. Including the // "unix" option signals that the field should be encoded as a Unix time (see -// time.Unix()) +// time.Unix()). The "unixmilli" and "unixnano" options will encode the number +// of milliseconds and nanoseconds, respectively, since January 1, 1970 (see +// time.UnixNano()). Including the "layout" struct tag (separate from the +// "url" tag) will use the value of the "layout" tag as a layout passed to +// time.Format. For example: +// +// // Encode a time.Time as YYYY-MM-DD +// Field time.Time `layout:"2006-01-02"` // // Slice and Array values default to encoding as multiple URL values of the // same name. Including the "comma" option signals that the field should be @@ -92,7 +99,13 @@ type Encoder interface { // Including the "brackets" option signals that the multiple URL values should // have "[]" appended to the value name. "numbered" will append a number to // the end of each incidence of the value name, example: -// name0=value0&name1=value1, etc. +// name0=value0&name1=value1, etc. Including the "del" struct tag (separate +// from the "url" tag) will use the value of the "del" tag as the delimiter. +// For example: +// +// // Encode a slice of bools as ints ("1" for true, "0" for false), +// // separated by exclamation points "!". +// Field []bool `url:",int" del:"!"` // // Anonymous struct fields are usually encoded as if their inner exported // fields were fields in the outer struct, subject to the standard Go @@ -151,11 +164,15 @@ func reflectValue(values url.Values, val reflect.Value, scope string) error { continue } name, opts := parseTag(tag) + if name == "" { - if sf.Anonymous && sv.Kind() == reflect.Struct { - // save embedded struct for later processing - embedded = append(embedded, sv) - continue + if sf.Anonymous { + v := reflect.Indirect(sv) + if v.IsValid() && v.Kind() == reflect.Struct { + // save embedded struct for later processing + embedded = append(embedded, v) + continue + } } name = sf.Name @@ -170,7 +187,9 @@ func reflectValue(values url.Values, val reflect.Value, scope string) error { } if sv.Type().Implements(encoderType) { - if !reflect.Indirect(sv).IsValid() { + // if sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(encoderType) { sv = reflect.New(sv.Type().Elem()) } @@ -181,28 +200,38 @@ func reflectValue(values url.Values, val reflect.Value, scope string) error { continue } + // recursively dereference pointers. break on nil pointers + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { - var del byte + var del string if opts.Contains("comma") { - del = ',' + del = "," } else if opts.Contains("space") { - del = ' ' + del = " " } else if opts.Contains("semicolon") { - del = ';' + del = ";" } else if opts.Contains("brackets") { name = name + "[]" + } else { + del = sf.Tag.Get("del") } - if del != 0 { + if del != "" { s := new(bytes.Buffer) first := true for i := 0; i < sv.Len(); i++ { if first { first = false } else { - s.WriteByte(del) + s.WriteString(del) } - s.WriteString(valueString(sv.Index(i), opts)) + s.WriteString(valueString(sv.Index(i), opts, sf)) } values.Add(name, s.String()) } else { @@ -211,30 +240,25 @@ func reflectValue(values url.Values, val reflect.Value, scope string) error { if opts.Contains("numbered") { k = fmt.Sprintf("%s%d", name, i) } - values.Add(k, valueString(sv.Index(i), opts)) + values.Add(k, valueString(sv.Index(i), opts, sf)) } } continue } - for sv.Kind() == reflect.Ptr { - if sv.IsNil() { - break - } - sv = sv.Elem() - } - if sv.Type() == timeType { - values.Add(name, valueString(sv, opts)) + values.Add(name, valueString(sv, opts, sf)) continue } if sv.Kind() == reflect.Struct { - reflectValue(values, sv, name) + if err := reflectValue(values, sv, name); err != nil { + return err + } continue } - values.Add(name, valueString(sv, opts)) + values.Add(name, valueString(sv, opts, sf)) } for _, f := range embedded { @@ -247,7 +271,7 @@ func reflectValue(values url.Values, val reflect.Value, scope string) error { } // valueString returns the string representation of a value. -func valueString(v reflect.Value, opts tagOptions) string { +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { for v.Kind() == reflect.Ptr { if v.IsNil() { return "" @@ -267,6 +291,15 @@ func valueString(v reflect.Value, opts tagOptions) string { if opts.Contains("unix") { return strconv.FormatInt(t.Unix(), 10) } + if opts.Contains("unixmilli") { + return strconv.FormatInt((t.UnixNano() / 1e6), 10) + } + if opts.Contains("unixnano") { + return strconv.FormatInt(t.UnixNano(), 10) + } + if layout := sf.Tag.Get("layout"); layout != "" { + return t.Format(layout) + } return t.Format(time.RFC3339) } @@ -291,8 +324,12 @@ func isEmptyValue(v reflect.Value) bool { return v.IsNil() } - if v.Type() == timeType { - return v.Interface().(time.Time).IsZero() + type zeroable interface { + IsZero() bool + } + + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() } return false diff --git a/vendor/github.com/gorilla/websocket/.gitignore b/vendor/github.com/gorilla/websocket/.gitignore index ac71020..cd3fcd1 100644 --- a/vendor/github.com/gorilla/websocket/.gitignore +++ b/vendor/github.com/gorilla/websocket/.gitignore @@ -22,4 +22,4 @@ _testmain.go *.exe .idea/ -*.iml \ No newline at end of file +*.iml diff --git a/vendor/github.com/gorilla/websocket/.travis.yml b/vendor/github.com/gorilla/websocket/.travis.yml deleted file mode 100644 index 3d8d29c..0000000 --- a/vendor/github.com/gorilla/websocket/.travis.yml +++ /dev/null @@ -1,19 +0,0 @@ -language: go -sudo: false - -matrix: - include: - - go: 1.4 - - go: 1.5 - - go: 1.6 - - go: 1.7 - - go: 1.8 - - go: tip - allow_failures: - - go: tip - -script: - - go get -t -v ./... - - diff -u <(echo -n) <(gofmt -d .) - - go vet $(go list ./... | grep -v /vendor/) - - go test -v -race ./... diff --git a/vendor/github.com/gorilla/websocket/AUTHORS b/vendor/github.com/gorilla/websocket/AUTHORS index b003eca..1931f40 100644 --- a/vendor/github.com/gorilla/websocket/AUTHORS +++ b/vendor/github.com/gorilla/websocket/AUTHORS @@ -4,5 +4,6 @@ # Please keep the list sorted. Gary Burd +Google LLC (https://opensource.google.com/) Joachim Bauch diff --git a/vendor/github.com/gorilla/websocket/README.md b/vendor/github.com/gorilla/websocket/README.md index 33c3d2b..19aa2e7 100644 --- a/vendor/github.com/gorilla/websocket/README.md +++ b/vendor/github.com/gorilla/websocket/README.md @@ -1,14 +1,14 @@ # Gorilla WebSocket +[![GoDoc](https://godoc.org/github.com/gorilla/websocket?status.svg)](https://godoc.org/github.com/gorilla/websocket) +[![CircleCI](https://circleci.com/gh/gorilla/websocket.svg?style=svg)](https://circleci.com/gh/gorilla/websocket) + Gorilla WebSocket is a [Go](http://golang.org/) implementation of the [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. -[![Build Status](https://travis-ci.org/gorilla/websocket.svg?branch=master)](https://travis-ci.org/gorilla/websocket) -[![GoDoc](https://godoc.org/github.com/gorilla/websocket?status.svg)](https://godoc.org/github.com/gorilla/websocket) - ### Documentation -* [API Reference](http://godoc.org/github.com/gorilla/websocket) +* [API Reference](https://pkg.go.dev/github.com/gorilla/websocket?tab=doc) * [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat) * [Command example](https://github.com/gorilla/websocket/tree/master/examples/command) * [Client and server example](https://github.com/gorilla/websocket/tree/master/examples/echo) @@ -27,7 +27,7 @@ package API is stable. ### Protocol Compliance The Gorilla WebSocket package passes the server tests in the [Autobahn Test -Suite](http://autobahn.ws/testsuite) using the application in the [examples/autobahn +Suite](https://github.com/crossbario/autobahn-testsuite) using the application in the [examples/autobahn subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn). ### Gorilla WebSocket compared with other packages @@ -40,7 +40,7 @@ subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn RFC 6455 Features -Passes Autobahn Test SuiteYesNo +Passes Autobahn Test SuiteYesNo Receive fragmented messageYesNo, see note 1 Send close messageYesNo Send pings and receive pongsYesNo @@ -51,7 +51,7 @@ subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn Write message using io.WriteCloserYesNo, see note 3 -Notes: +Notes: 1. Large messages are fragmented in [Chrome's new WebSocket implementation](http://www.ietf.org/mail-archive/web/hybi/current/msg10503.html). 2. The application can get the type of a received data message by implementing diff --git a/vendor/github.com/gorilla/websocket/client.go b/vendor/github.com/gorilla/websocket/client.go index 43a87c7..962c06a 100644 --- a/vendor/github.com/gorilla/websocket/client.go +++ b/vendor/github.com/gorilla/websocket/client.go @@ -5,15 +5,15 @@ package websocket import ( - "bufio" "bytes" + "context" "crypto/tls" - "encoding/base64" "errors" "io" "io/ioutil" "net" "net/http" + "net/http/httptrace" "net/url" "strings" "time" @@ -53,6 +53,10 @@ type Dialer struct { // NetDial is nil, net.Dial is used. NetDial func(network, addr string) (net.Conn, error) + // NetDialContext specifies the dial function for creating TCP connections. If + // NetDialContext is nil, net.DialContext is used. + NetDialContext func(ctx context.Context, network, addr string) (net.Conn, error) + // Proxy specifies a function to return a proxy for a given // Request. If the function returns a non-nil error, the // request is aborted with the provided error. @@ -66,11 +70,22 @@ type Dialer struct { // HandshakeTimeout specifies the duration for the handshake to complete. HandshakeTimeout time.Duration - // ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer + // ReadBufferSize and WriteBufferSize specify I/O buffer sizes in bytes. If a buffer // size is zero, then a useful default size is used. The I/O buffer sizes // do not limit the size of the messages that can be sent or received. ReadBufferSize, WriteBufferSize int + // WriteBufferPool is a pool of buffers for write operations. If the value + // is not set, then write buffers are allocated to the connection for the + // lifetime of the connection. + // + // A pool is most useful when the application has a modest volume of writes + // across a large number of connections. + // + // Applications should use a single pool for each unique value of + // WriteBufferSize. + WriteBufferPool BufferPool + // Subprotocols specifies the client's requested subprotocols. Subprotocols []string @@ -86,52 +101,13 @@ type Dialer struct { Jar http.CookieJar } -var errMalformedURL = errors.New("malformed ws or wss URL") - -// parseURL parses the URL. -// -// This function is a replacement for the standard library url.Parse function. -// In Go 1.4 and earlier, url.Parse loses information from the path. -func parseURL(s string) (*url.URL, error) { - // From the RFC: - // - // ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ] - // wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ] - var u url.URL - switch { - case strings.HasPrefix(s, "ws://"): - u.Scheme = "ws" - s = s[len("ws://"):] - case strings.HasPrefix(s, "wss://"): - u.Scheme = "wss" - s = s[len("wss://"):] - default: - return nil, errMalformedURL - } - - if i := strings.Index(s, "?"); i >= 0 { - u.RawQuery = s[i+1:] - s = s[:i] - } - - if i := strings.Index(s, "/"); i >= 0 { - u.Opaque = s[i:] - s = s[:i] - } else { - u.Opaque = "/" - } - - u.Host = s - - if strings.Contains(u.Host, "@") { - // Don't bother parsing user information because user information is - // not allowed in websocket URIs. - return nil, errMalformedURL - } - - return &u, nil +// Dial creates a new client connection by calling DialContext with a background context. +func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) { + return d.DialContext(context.Background(), urlStr, requestHeader) } +var errMalformedURL = errors.New("malformed ws or wss URL") + func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) { hostPort = u.Host hostNoPort = u.Host @@ -150,26 +126,29 @@ func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) { return hostPort, hostNoPort } -// DefaultDialer is a dialer with all fields set to the default zero values. +// DefaultDialer is a dialer with all fields set to the default values. var DefaultDialer = &Dialer{ - Proxy: http.ProxyFromEnvironment, + Proxy: http.ProxyFromEnvironment, + HandshakeTimeout: 45 * time.Second, } -// Dial creates a new client connection. Use requestHeader to specify the +// nilDialer is dialer to use when receiver is nil. +var nilDialer = *DefaultDialer + +// DialContext creates a new client connection. Use requestHeader to specify the // origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie). // Use the response.Header to get the selected subprotocol // (Sec-WebSocket-Protocol) and cookies (Set-Cookie). // +// The context will be used in the request and in the Dialer. +// // If the WebSocket handshake fails, ErrBadHandshake is returned along with a // non-nil *http.Response so that callers can handle redirects, authentication, // etcetera. The response body may not contain the entire response and does not // need to be closed by the application. -func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) { - +func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) { if d == nil { - d = &Dialer{ - Proxy: http.ProxyFromEnvironment, - } + d = &nilDialer } challengeKey, err := generateChallengeKey() @@ -177,7 +156,7 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re return nil, nil, err } - u, err := parseURL(urlStr) + u, err := url.Parse(urlStr) if err != nil { return nil, nil, err } @@ -205,6 +184,7 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re Header: make(http.Header), Host: u.Host, } + req = req.WithContext(ctx) // Set the cookies present in the cookie jar of the dialer if d.Jar != nil { @@ -237,45 +217,83 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re k == "Sec-Websocket-Extensions" || (k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0): return nil, nil, errors.New("websocket: duplicate header not allowed: " + k) + case k == "Sec-Websocket-Protocol": + req.Header["Sec-WebSocket-Protocol"] = vs default: req.Header[k] = vs } } if d.EnableCompression { - req.Header.Set("Sec-Websocket-Extensions", "permessage-deflate; server_no_context_takeover; client_no_context_takeover") + req.Header["Sec-WebSocket-Extensions"] = []string{"permessage-deflate; server_no_context_takeover; client_no_context_takeover"} } - hostPort, hostNoPort := hostPortNoPort(u) - - var proxyURL *url.URL - // Check wether the proxy method has been configured - if d.Proxy != nil { - proxyURL, err = d.Proxy(req) - } - if err != nil { - return nil, nil, err + if d.HandshakeTimeout != 0 { + var cancel func() + ctx, cancel = context.WithTimeout(ctx, d.HandshakeTimeout) + defer cancel() } - var targetHostPort string - if proxyURL != nil { - targetHostPort, _ = hostPortNoPort(proxyURL) + // Get network dial function. + var netDial func(network, add string) (net.Conn, error) + + if d.NetDialContext != nil { + netDial = func(network, addr string) (net.Conn, error) { + return d.NetDialContext(ctx, network, addr) + } + } else if d.NetDial != nil { + netDial = d.NetDial } else { - targetHostPort = hostPort + netDialer := &net.Dialer{} + netDial = func(network, addr string) (net.Conn, error) { + return netDialer.DialContext(ctx, network, addr) + } } - var deadline time.Time - if d.HandshakeTimeout != 0 { - deadline = time.Now().Add(d.HandshakeTimeout) + // If needed, wrap the dial function to set the connection deadline. + if deadline, ok := ctx.Deadline(); ok { + forwardDial := netDial + netDial = func(network, addr string) (net.Conn, error) { + c, err := forwardDial(network, addr) + if err != nil { + return nil, err + } + err = c.SetDeadline(deadline) + if err != nil { + c.Close() + return nil, err + } + return c, nil + } } - netDial := d.NetDial - if netDial == nil { - netDialer := &net.Dialer{Deadline: deadline} - netDial = netDialer.Dial + // If needed, wrap the dial function to connect through a proxy. + if d.Proxy != nil { + proxyURL, err := d.Proxy(req) + if err != nil { + return nil, nil, err + } + if proxyURL != nil { + dialer, err := proxy_FromURL(proxyURL, netDialerFunc(netDial)) + if err != nil { + return nil, nil, err + } + netDial = dialer.Dial + } + } + + hostPort, hostNoPort := hostPortNoPort(u) + trace := httptrace.ContextClientTrace(ctx) + if trace != nil && trace.GetConn != nil { + trace.GetConn(hostPort) } - netConn, err := netDial("tcp", targetHostPort) + netConn, err := netDial("tcp", hostPort) + if trace != nil && trace.GotConn != nil { + trace.GotConn(httptrace.GotConnInfo{ + Conn: netConn, + }) + } if err != nil { return nil, nil, err } @@ -286,42 +304,6 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re } }() - if err := netConn.SetDeadline(deadline); err != nil { - return nil, nil, err - } - - if proxyURL != nil { - connectHeader := make(http.Header) - if user := proxyURL.User; user != nil { - proxyUser := user.Username() - if proxyPassword, passwordSet := user.Password(); passwordSet { - credential := base64.StdEncoding.EncodeToString([]byte(proxyUser + ":" + proxyPassword)) - connectHeader.Set("Proxy-Authorization", "Basic "+credential) - } - } - connectReq := &http.Request{ - Method: "CONNECT", - URL: &url.URL{Opaque: hostPort}, - Host: hostPort, - Header: connectHeader, - } - - connectReq.Write(netConn) - - // Read response. - // Okay to use and discard buffered reader here, because - // TLS server will not speak until spoken to. - br := bufio.NewReader(netConn) - resp, err := http.ReadResponse(br, connectReq) - if err != nil { - return nil, nil, err - } - if resp.StatusCode != 200 { - f := strings.SplitN(resp.Status, " ", 2) - return nil, nil, errors.New(f[1]) - } - } - if u.Scheme == "https" { cfg := cloneTLSConfig(d.TLSClientConfig) if cfg.ServerName == "" { @@ -329,22 +311,31 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re } tlsConn := tls.Client(netConn, cfg) netConn = tlsConn - if err := tlsConn.Handshake(); err != nil { - return nil, nil, err + + var err error + if trace != nil { + err = doHandshakeWithTrace(trace, tlsConn, cfg) + } else { + err = doHandshake(tlsConn, cfg) } - if !cfg.InsecureSkipVerify { - if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil { - return nil, nil, err - } + + if err != nil { + return nil, nil, err } } - conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize) + conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize, d.WriteBufferPool, nil, nil) if err := req.Write(netConn); err != nil { return nil, nil, err } + if trace != nil && trace.GotFirstResponseByte != nil { + if peek, err := conn.br.Peek(1); err == nil && len(peek) == 1 { + trace.GotFirstResponseByte() + } + } + resp, err := http.ReadResponse(conn.br, req) if err != nil { return nil, nil, err @@ -390,3 +381,15 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re netConn = nil // to avoid close in defer. return conn, resp, nil } + +func doHandshake(tlsConn *tls.Conn, cfg *tls.Config) error { + if err := tlsConn.Handshake(); err != nil { + return err + } + if !cfg.InsecureSkipVerify { + if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/gorilla/websocket/conn.go b/vendor/github.com/gorilla/websocket/conn.go index 97e1dba..ca46d2f 100644 --- a/vendor/github.com/gorilla/websocket/conn.go +++ b/vendor/github.com/gorilla/websocket/conn.go @@ -76,7 +76,7 @@ const ( // is UTF-8 encoded text. PingMessage = 9 - // PongMessage denotes a ping control message. The optional message payload + // PongMessage denotes a pong control message. The optional message payload // is UTF-8 encoded text. PongMessage = 10 ) @@ -100,9 +100,8 @@ func (e *netError) Error() string { return e.msg } func (e *netError) Temporary() bool { return e.temporary } func (e *netError) Timeout() bool { return e.timeout } -// CloseError represents close frame. +// CloseError represents a close message. type CloseError struct { - // Code is defined in RFC 6455, section 11.7. Code int @@ -224,6 +223,20 @@ func isValidReceivedCloseCode(code int) bool { return validReceivedCloseCodes[code] || (code >= 3000 && code <= 4999) } +// BufferPool represents a pool of buffers. The *sync.Pool type satisfies this +// interface. The type of the value stored in a pool is not specified. +type BufferPool interface { + // Get gets a value from the pool or returns nil if the pool is empty. + Get() interface{} + // Put adds a value to the pool. + Put(interface{}) +} + +// writePoolData is the type added to the write buffer pool. This wrapper is +// used to prevent applications from peeking at and depending on the values +// added to the pool. +type writePoolData struct{ buf []byte } + // The Conn type represents a WebSocket connection. type Conn struct { conn net.Conn @@ -231,8 +244,10 @@ type Conn struct { subprotocol string // Write fields - mu chan bool // used as mutex to protect write to conn - writeBuf []byte // frame is constructed in this buffer. + mu chan struct{} // used as mutex to protect write to conn + writeBuf []byte // frame is constructed in this buffer. + writePool BufferPool + writeBufSize int writeDeadline time.Time writer io.WriteCloser // the current writer returned to the application isWriting bool // for best-effort concurrent write detection @@ -245,10 +260,12 @@ type Conn struct { newCompressionWriter func(io.WriteCloser, int) io.WriteCloser // Read fields - reader io.ReadCloser // the current reader returned to the application - readErr error - br *bufio.Reader - readRemaining int64 // bytes remaining in current frame. + reader io.ReadCloser // the current reader returned to the application + readErr error + br *bufio.Reader + // bytes remaining in current frame. + // set setReadRemaining to safely update this value and prevent overflow + readRemaining int64 readFinal bool // true the current message has more frames. readLength int64 // Message size. readLimit int64 // Maximum message size. @@ -264,64 +281,29 @@ type Conn struct { newDecompressionReader func(io.Reader) io.ReadCloser } -func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int) *Conn { - return newConnBRW(conn, isServer, readBufferSize, writeBufferSize, nil) -} - -type writeHook struct { - p []byte -} - -func (wh *writeHook) Write(p []byte) (int, error) { - wh.p = p - return len(p), nil -} +func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int, writeBufferPool BufferPool, br *bufio.Reader, writeBuf []byte) *Conn { -func newConnBRW(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int, brw *bufio.ReadWriter) *Conn { - mu := make(chan bool, 1) - mu <- true - - var br *bufio.Reader - if readBufferSize == 0 && brw != nil && brw.Reader != nil { - // Reuse the supplied bufio.Reader if the buffer has a useful size. - // This code assumes that peek on a reader returns - // bufio.Reader.buf[:0]. - brw.Reader.Reset(conn) - if p, err := brw.Reader.Peek(0); err == nil && cap(p) >= 256 { - br = brw.Reader - } - } if br == nil { if readBufferSize == 0 { readBufferSize = defaultReadBufferSize - } - if readBufferSize < maxControlFramePayloadSize { + } else if readBufferSize < maxControlFramePayloadSize { + // must be large enough for control frame readBufferSize = maxControlFramePayloadSize } br = bufio.NewReaderSize(conn, readBufferSize) } - var writeBuf []byte - if writeBufferSize == 0 && brw != nil && brw.Writer != nil { - // Use the bufio.Writer's buffer if the buffer has a useful size. This - // code assumes that bufio.Writer.buf[:1] is passed to the - // bufio.Writer's underlying writer. - var wh writeHook - brw.Writer.Reset(&wh) - brw.Writer.WriteByte(0) - brw.Flush() - if cap(wh.p) >= maxFrameHeaderSize+256 { - writeBuf = wh.p[:cap(wh.p)] - } + if writeBufferSize <= 0 { + writeBufferSize = defaultWriteBufferSize } + writeBufferSize += maxFrameHeaderSize - if writeBuf == nil { - if writeBufferSize == 0 { - writeBufferSize = defaultWriteBufferSize - } - writeBuf = make([]byte, writeBufferSize+maxFrameHeaderSize) + if writeBuf == nil && writeBufferPool == nil { + writeBuf = make([]byte, writeBufferSize) } + mu := make(chan struct{}, 1) + mu <- struct{}{} c := &Conn{ isServer: isServer, br: br, @@ -329,6 +311,8 @@ func newConnBRW(conn net.Conn, isServer bool, readBufferSize, writeBufferSize in mu: mu, readFinal: true, writeBuf: writeBuf, + writePool: writeBufferPool, + writeBufSize: writeBufferSize, enableWriteCompression: true, compressionLevel: defaultCompressionLevel, } @@ -338,12 +322,24 @@ func newConnBRW(conn net.Conn, isServer bool, readBufferSize, writeBufferSize in return c } +// setReadRemaining tracks the number of bytes remaining on the connection. If n +// overflows, an ErrReadLimit is returned. +func (c *Conn) setReadRemaining(n int64) error { + if n < 0 { + return ErrReadLimit + } + + c.readRemaining = n + return nil +} + // Subprotocol returns the negotiated protocol for the connection. func (c *Conn) Subprotocol() string { return c.subprotocol } -// Close closes the underlying network connection without sending or waiting for a close frame. +// Close closes the underlying network connection without sending or waiting +// for a close message. func (c *Conn) Close() error { return c.conn.Close() } @@ -370,9 +366,18 @@ func (c *Conn) writeFatal(err error) error { return err } -func (c *Conn) write(frameType int, deadline time.Time, bufs ...[]byte) error { +func (c *Conn) read(n int) ([]byte, error) { + p, err := c.br.Peek(n) + if err == io.EOF { + err = errUnexpectedEOF + } + c.br.Discard(len(p)) + return p, err +} + +func (c *Conn) write(frameType int, deadline time.Time, buf0, buf1 []byte) error { <-c.mu - defer func() { c.mu <- true }() + defer func() { c.mu <- struct{}{} }() c.writeErrMu.Lock() err := c.writeErr @@ -382,15 +387,14 @@ func (c *Conn) write(frameType int, deadline time.Time, bufs ...[]byte) error { } c.conn.SetWriteDeadline(deadline) - for _, buf := range bufs { - if len(buf) > 0 { - _, err := c.conn.Write(buf) - if err != nil { - return c.writeFatal(err) - } - } + if len(buf1) == 0 { + _, err = c.conn.Write(buf0) + } else { + err = c.writeBufs(buf0, buf1) + } + if err != nil { + return c.writeFatal(err) } - if frameType == CloseMessage { c.writeFatal(ErrCloseSent) } @@ -425,7 +429,7 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er maskBytes(key, 0, buf[6:]) } - d := time.Hour * 1000 + d := 1000 * time.Hour if !deadline.IsZero() { d = deadline.Sub(time.Now()) if d < 0 { @@ -440,7 +444,7 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er case <-timer.C: return errWriteTimeout } - defer func() { c.mu <- true }() + defer func() { c.mu <- struct{}{} }() c.writeErrMu.Lock() err := c.writeErr @@ -460,7 +464,8 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er return err } -func (c *Conn) prepWrite(messageType int) error { +// beginMessage prepares a connection and message writer for a new message. +func (c *Conn) beginMessage(mw *messageWriter, messageType int) error { // Close previous writer if not already closed by the application. It's // probably better to return an error in this situation, but we cannot // change this without breaking existing applications. @@ -476,7 +481,23 @@ func (c *Conn) prepWrite(messageType int) error { c.writeErrMu.Lock() err := c.writeErr c.writeErrMu.Unlock() - return err + if err != nil { + return err + } + + mw.c = c + mw.frameType = messageType + mw.pos = maxFrameHeaderSize + + if c.writeBuf == nil { + wpd, ok := c.writePool.Get().(writePoolData) + if ok { + c.writeBuf = wpd.buf + } else { + c.writeBuf = make([]byte, c.writeBufSize) + } + } + return nil } // NextWriter returns a writer for the next message to send. The writer's Close @@ -484,17 +505,15 @@ func (c *Conn) prepWrite(messageType int) error { // // There can be at most one open writer on a connection. NextWriter closes the // previous writer if the application has not already done so. +// +// All message types (TextMessage, BinaryMessage, CloseMessage, PingMessage and +// PongMessage) are supported. func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) { - if err := c.prepWrite(messageType); err != nil { + var mw messageWriter + if err := c.beginMessage(&mw, messageType); err != nil { return nil, err } - - mw := &messageWriter{ - c: c, - frameType: messageType, - pos: maxFrameHeaderSize, - } - c.writer = mw + c.writer = &mw if c.newCompressionWriter != nil && c.enableWriteCompression && isData(messageType) { w := c.newCompressionWriter(c.writer, c.compressionLevel) mw.compress = true @@ -511,10 +530,16 @@ type messageWriter struct { err error } -func (w *messageWriter) fatal(err error) error { +func (w *messageWriter) endMessage(err error) error { if w.err != nil { - w.err = err - w.c.writer = nil + return err + } + c := w.c + w.err = err + c.writer = nil + if c.writePool != nil { + c.writePool.Put(writePoolData{buf: c.writeBuf}) + c.writeBuf = nil } return err } @@ -528,7 +553,7 @@ func (w *messageWriter) flushFrame(final bool, extra []byte) error { // Check for invalid control frames. if isControl(w.frameType) && (!final || length > maxControlFramePayloadSize) { - return w.fatal(errInvalidControlFrame) + return w.endMessage(errInvalidControlFrame) } b0 := byte(w.frameType) @@ -573,7 +598,7 @@ func (w *messageWriter) flushFrame(final bool, extra []byte) error { copy(c.writeBuf[maxFrameHeaderSize-4:], key[:]) maskBytes(key, 0, c.writeBuf[maxFrameHeaderSize:w.pos]) if len(extra) > 0 { - return c.writeFatal(errors.New("websocket: internal error, extra used in client mode")) + return w.endMessage(c.writeFatal(errors.New("websocket: internal error, extra used in client mode"))) } } @@ -594,11 +619,11 @@ func (w *messageWriter) flushFrame(final bool, extra []byte) error { c.isWriting = false if err != nil { - return w.fatal(err) + return w.endMessage(err) } if final { - c.writer = nil + w.endMessage(errWriteClosed) return nil } @@ -696,11 +721,7 @@ func (w *messageWriter) Close() error { if w.err != nil { return w.err } - if err := w.flushFrame(true, nil); err != nil { - return err - } - w.err = errWriteClosed - return nil + return w.flushFrame(true, nil) } // WritePreparedMessage writes prepared message into connection. @@ -732,10 +753,10 @@ func (c *Conn) WriteMessage(messageType int, data []byte) error { if c.isServer && (c.newCompressionWriter == nil || !c.enableWriteCompression) { // Fast path with no allocations and single frame. - if err := c.prepWrite(messageType); err != nil { + var mw messageWriter + if err := c.beginMessage(&mw, messageType); err != nil { return err } - mw := messageWriter{c: c, frameType: messageType, pos: maxFrameHeaderSize} n := copy(c.writeBuf[mw.pos:], data) mw.pos += n data = data[n:] @@ -764,7 +785,6 @@ func (c *Conn) SetWriteDeadline(t time.Time) error { // Read methods func (c *Conn) advanceFrame() (int, error) { - // 1. Skip remainder of previous frame. if c.readRemaining > 0 { @@ -783,7 +803,7 @@ func (c *Conn) advanceFrame() (int, error) { final := p[0]&finalBit != 0 frameType := int(p[0] & 0xf) mask := p[1]&maskBit != 0 - c.readRemaining = int64(p[1] & 0x7f) + c.setReadRemaining(int64(p[1] & 0x7f)) c.readDecompress = false if c.newDecompressionReader != nil && (p[0]&rsv1Bit) != 0 { @@ -817,7 +837,17 @@ func (c *Conn) advanceFrame() (int, error) { return noFrame, c.handleProtocolError("unknown opcode " + strconv.Itoa(frameType)) } - // 3. Read and parse frame length. + // 3. Read and parse frame length as per + // https://tools.ietf.org/html/rfc6455#section-5.2 + // + // The length of the "Payload data", in bytes: if 0-125, that is the payload + // length. + // - If 126, the following 2 bytes interpreted as a 16-bit unsigned + // integer are the payload length. + // - If 127, the following 8 bytes interpreted as + // a 64-bit unsigned integer (the most significant bit MUST be 0) are the + // payload length. Multibyte length quantities are expressed in network byte + // order. switch c.readRemaining { case 126: @@ -825,13 +855,19 @@ func (c *Conn) advanceFrame() (int, error) { if err != nil { return noFrame, err } - c.readRemaining = int64(binary.BigEndian.Uint16(p)) + + if err := c.setReadRemaining(int64(binary.BigEndian.Uint16(p))); err != nil { + return noFrame, err + } case 127: p, err := c.read(8) if err != nil { return noFrame, err } - c.readRemaining = int64(binary.BigEndian.Uint64(p)) + + if err := c.setReadRemaining(int64(binary.BigEndian.Uint64(p))); err != nil { + return noFrame, err + } } // 4. Handle frame masking. @@ -854,6 +890,12 @@ func (c *Conn) advanceFrame() (int, error) { if frameType == continuationFrame || frameType == TextMessage || frameType == BinaryMessage { c.readLength += c.readRemaining + // Don't allow readLength to overflow in the presence of a large readRemaining + // counter. + if c.readLength < 0 { + return noFrame, ErrReadLimit + } + if c.readLimit > 0 && c.readLength > c.readLimit { c.WriteControl(CloseMessage, FormatCloseMessage(CloseMessageTooBig, ""), time.Now().Add(writeWait)) return noFrame, ErrReadLimit @@ -867,7 +909,7 @@ func (c *Conn) advanceFrame() (int, error) { var payload []byte if c.readRemaining > 0 { payload, err = c.read(int(c.readRemaining)) - c.readRemaining = 0 + c.setReadRemaining(0) if err != nil { return noFrame, err } @@ -940,6 +982,7 @@ func (c *Conn) NextReader() (messageType int, r io.Reader, err error) { c.readErr = hideTempErr(err) break } + if frameType == TextMessage || frameType == BinaryMessage { c.messageReader = &messageReader{c} c.reader = c.messageReader @@ -980,7 +1023,9 @@ func (r *messageReader) Read(b []byte) (int, error) { if c.isServer { c.readMaskPos = maskBytes(c.readMaskKey, c.readMaskPos, b[:n]) } - c.readRemaining -= int64(n) + rem := c.readRemaining + rem -= int64(n) + c.setReadRemaining(rem) if c.readRemaining > 0 && c.readErr == io.EOF { c.readErr = errUnexpectedEOF } @@ -1032,8 +1077,8 @@ func (c *Conn) SetReadDeadline(t time.Time) error { return c.conn.SetReadDeadline(t) } -// SetReadLimit sets the maximum size for a message read from the peer. If a -// message exceeds the limit, the connection sends a close frame to the peer +// SetReadLimit sets the maximum size in bytes for a message read from the peer. If a +// message exceeds the limit, the connection sends a close message to the peer // and returns ErrReadLimit to the application. func (c *Conn) SetReadLimit(limit int64) { c.readLimit = limit @@ -1046,24 +1091,22 @@ func (c *Conn) CloseHandler() func(code int, text string) error { // SetCloseHandler sets the handler for close messages received from the peer. // The code argument to h is the received close code or CloseNoStatusReceived -// if the close message is empty. The default close handler sends a close frame -// back to the peer. +// if the close message is empty. The default close handler sends a close +// message back to the peer. // -// The application must read the connection to process close messages as -// described in the section on Control Frames above. +// The handler function is called from the NextReader, ReadMessage and message +// reader Read methods. The application must read the connection to process +// close messages as described in the section on Control Messages above. // -// The connection read methods return a CloseError when a close frame is +// The connection read methods return a CloseError when a close message is // received. Most applications should handle close messages as part of their // normal error handling. Applications should only set a close handler when the -// application must perform some action before sending a close frame back to +// application must perform some action before sending a close message back to // the peer. func (c *Conn) SetCloseHandler(h func(code int, text string) error) { if h == nil { h = func(code int, text string) error { - message := []byte{} - if code != CloseNoStatusReceived { - message = FormatCloseMessage(code, "") - } + message := FormatCloseMessage(code, "") c.WriteControl(CloseMessage, message, time.Now().Add(writeWait)) return nil } @@ -1077,11 +1120,12 @@ func (c *Conn) PingHandler() func(appData string) error { } // SetPingHandler sets the handler for ping messages received from the peer. -// The appData argument to h is the PING frame application data. The default +// The appData argument to h is the PING message application data. The default // ping handler sends a pong to the peer. // -// The application must read the connection to process ping messages as -// described in the section on Control Frames above. +// The handler function is called from the NextReader, ReadMessage and message +// reader Read methods. The application must read the connection to process +// ping messages as described in the section on Control Messages above. func (c *Conn) SetPingHandler(h func(appData string) error) { if h == nil { h = func(message string) error { @@ -1103,11 +1147,12 @@ func (c *Conn) PongHandler() func(appData string) error { } // SetPongHandler sets the handler for pong messages received from the peer. -// The appData argument to h is the PONG frame application data. The default +// The appData argument to h is the PONG message application data. The default // pong handler does nothing. // -// The application must read the connection to process ping messages as -// described in the section on Control Frames above. +// The handler function is called from the NextReader, ReadMessage and message +// reader Read methods. The application must read the connection to process +// pong messages as described in the section on Control Messages above. func (c *Conn) SetPongHandler(h func(appData string) error) { if h == nil { h = func(string) error { return nil } @@ -1141,7 +1186,14 @@ func (c *Conn) SetCompressionLevel(level int) error { } // FormatCloseMessage formats closeCode and text as a WebSocket close message. +// An empty message is returned for code CloseNoStatusReceived. func FormatCloseMessage(closeCode int, text string) []byte { + if closeCode == CloseNoStatusReceived { + // Return empty message because it's illegal to send + // CloseNoStatusReceived. Return non-nil value in case application + // checks for nil. + return []byte{} + } buf := make([]byte, 2+len(text)) binary.BigEndian.PutUint16(buf, uint16(closeCode)) copy(buf[2:], text) diff --git a/vendor/github.com/gorilla/websocket/conn_read_legacy.go b/vendor/github.com/gorilla/websocket/conn_read_legacy.go deleted file mode 100644 index 018541c..0000000 --- a/vendor/github.com/gorilla/websocket/conn_read_legacy.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !go1.5 - -package websocket - -import "io" - -func (c *Conn) read(n int) ([]byte, error) { - p, err := c.br.Peek(n) - if err == io.EOF { - err = errUnexpectedEOF - } - if len(p) > 0 { - // advance over the bytes just read - io.ReadFull(c.br, p) - } - return p, err -} diff --git a/vendor/github.com/gorilla/websocket/conn_read.go b/vendor/github.com/gorilla/websocket/conn_write.go similarity index 52% rename from vendor/github.com/gorilla/websocket/conn_read.go rename to vendor/github.com/gorilla/websocket/conn_write.go index 1ea1505..a509a21 100644 --- a/vendor/github.com/gorilla/websocket/conn_read.go +++ b/vendor/github.com/gorilla/websocket/conn_write.go @@ -2,17 +2,14 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build go1.5 +// +build go1.8 package websocket -import "io" +import "net" -func (c *Conn) read(n int) ([]byte, error) { - p, err := c.br.Peek(n) - if err == io.EOF { - err = errUnexpectedEOF - } - c.br.Discard(len(p)) - return p, err +func (c *Conn) writeBufs(bufs ...[]byte) error { + b := net.Buffers(bufs) + _, err := b.WriteTo(c.conn) + return err } diff --git a/vendor/github.com/gorilla/websocket/conn_write_legacy.go b/vendor/github.com/gorilla/websocket/conn_write_legacy.go new file mode 100644 index 0000000..37edaff --- /dev/null +++ b/vendor/github.com/gorilla/websocket/conn_write_legacy.go @@ -0,0 +1,18 @@ +// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !go1.8 + +package websocket + +func (c *Conn) writeBufs(bufs ...[]byte) error { + for _, buf := range bufs { + if len(buf) > 0 { + if _, err := c.conn.Write(buf); err != nil { + return err + } + } + } + return nil +} diff --git a/vendor/github.com/gorilla/websocket/doc.go b/vendor/github.com/gorilla/websocket/doc.go index e291a95..8db0cef 100644 --- a/vendor/github.com/gorilla/websocket/doc.go +++ b/vendor/github.com/gorilla/websocket/doc.go @@ -6,9 +6,8 @@ // // Overview // -// The Conn type represents a WebSocket connection. A server application uses -// the Upgrade function from an Upgrader object with a HTTP request handler -// to get a pointer to a Conn: +// The Conn type represents a WebSocket connection. A server application calls +// the Upgrader.Upgrade method from an HTTP request handler to get a *Conn: // // var upgrader = websocket.Upgrader{ // ReadBufferSize: 1024, @@ -31,10 +30,12 @@ // for { // messageType, p, err := conn.ReadMessage() // if err != nil { +// log.Println(err) // return // } -// if err = conn.WriteMessage(messageType, p); err != nil { -// return err +// if err := conn.WriteMessage(messageType, p); err != nil { +// log.Println(err) +// return // } // } // @@ -85,20 +86,26 @@ // and pong. Call the connection WriteControl, WriteMessage or NextWriter // methods to send a control message to the peer. // -// Connections handle received close messages by sending a close message to the -// peer and returning a *CloseError from the the NextReader, ReadMessage or the -// message Read method. +// Connections handle received close messages by calling the handler function +// set with the SetCloseHandler method and by returning a *CloseError from the +// NextReader, ReadMessage or the message Read method. The default close +// handler sends a close message to the peer. // -// Connections handle received ping and pong messages by invoking callback -// functions set with SetPingHandler and SetPongHandler methods. The callback -// functions are called from the NextReader, ReadMessage and the message Read -// methods. +// Connections handle received ping messages by calling the handler function +// set with the SetPingHandler method. The default ping handler sends a pong +// message to the peer. +// +// Connections handle received pong messages by calling the handler function +// set with the SetPongHandler method. The default pong handler does nothing. +// If an application sends ping messages, then the application should set a +// pong handler to receive the corresponding pong. // -// The default ping handler sends a pong to the peer. The application's reading -// goroutine can block for a short time while the handler writes the pong data -// to the connection. +// The control message handler functions are called from the NextReader, +// ReadMessage and message reader Read methods. The default close and ping +// handlers can block these methods for a short time when the handler writes to +// the connection. // -// The application must read the connection to process ping, pong and close +// The application must read the connection to process close, ping and pong // messages sent from the peer. If the application is not otherwise interested // in messages from the peer, then the application should start a goroutine to // read and discard messages from the peer. A simple example is: @@ -137,19 +144,59 @@ // method fails the WebSocket handshake with HTTP status 403. // // If the CheckOrigin field is nil, then the Upgrader uses a safe default: fail -// the handshake if the Origin request header is present and not equal to the -// Host request header. -// -// An application can allow connections from any origin by specifying a -// function that always returns true: -// -// var upgrader = websocket.Upgrader{ -// CheckOrigin: func(r *http.Request) bool { return true }, -// } -// -// The deprecated Upgrade function does not enforce an origin policy. It's the -// application's responsibility to check the Origin header before calling -// Upgrade. +// the handshake if the Origin request header is present and the Origin host is +// not equal to the Host request header. +// +// The deprecated package-level Upgrade function does not perform origin +// checking. The application is responsible for checking the Origin header +// before calling the Upgrade function. +// +// Buffers +// +// Connections buffer network input and output to reduce the number +// of system calls when reading or writing messages. +// +// Write buffers are also used for constructing WebSocket frames. See RFC 6455, +// Section 5 for a discussion of message framing. A WebSocket frame header is +// written to the network each time a write buffer is flushed to the network. +// Decreasing the size of the write buffer can increase the amount of framing +// overhead on the connection. +// +// The buffer sizes in bytes are specified by the ReadBufferSize and +// WriteBufferSize fields in the Dialer and Upgrader. The Dialer uses a default +// size of 4096 when a buffer size field is set to zero. The Upgrader reuses +// buffers created by the HTTP server when a buffer size field is set to zero. +// The HTTP server buffers have a size of 4096 at the time of this writing. +// +// The buffer sizes do not limit the size of a message that can be read or +// written by a connection. +// +// Buffers are held for the lifetime of the connection by default. If the +// Dialer or Upgrader WriteBufferPool field is set, then a connection holds the +// write buffer only when writing a message. +// +// Applications should tune the buffer sizes to balance memory use and +// performance. Increasing the buffer size uses more memory, but can reduce the +// number of system calls to read or write the network. In the case of writing, +// increasing the buffer size can reduce the number of frame headers written to +// the network. +// +// Some guidelines for setting buffer parameters are: +// +// Limit the buffer sizes to the maximum expected message size. Buffers larger +// than the largest message do not provide any benefit. +// +// Depending on the distribution of message sizes, setting the buffer size to +// a value less than the maximum expected message size can greatly reduce memory +// use with a small impact on performance. Here's an example: If 99% of the +// messages are smaller than 256 bytes and the maximum message size is 512 +// bytes, then a buffer size of 256 bytes will result in 1.01 more system calls +// than a buffer size of 512 bytes. The memory savings is 50%. +// +// A write buffer pool is useful when the application has a modest number +// writes over a large number of connections. when buffers are pooled, a larger +// buffer size has a reduced impact on total memory use and has the benefit of +// reducing system calls and frame overhead. // // Compression EXPERIMENTAL // diff --git a/vendor/github.com/gorilla/websocket/join.go b/vendor/github.com/gorilla/websocket/join.go new file mode 100644 index 0000000..c64f8c8 --- /dev/null +++ b/vendor/github.com/gorilla/websocket/join.go @@ -0,0 +1,42 @@ +// Copyright 2019 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +import ( + "io" + "strings" +) + +// JoinMessages concatenates received messages to create a single io.Reader. +// The string term is appended to each message. The returned reader does not +// support concurrent calls to the Read method. +func JoinMessages(c *Conn, term string) io.Reader { + return &joinReader{c: c, term: term} +} + +type joinReader struct { + c *Conn + term string + r io.Reader +} + +func (r *joinReader) Read(p []byte) (int, error) { + if r.r == nil { + var err error + _, r.r, err = r.c.NextReader() + if err != nil { + return 0, err + } + if r.term != "" { + r.r = io.MultiReader(r.r, strings.NewReader(r.term)) + } + } + n, err := r.r.Read(p) + if err == io.EOF { + err = nil + r.r = nil + } + return n, err +} diff --git a/vendor/github.com/gorilla/websocket/json.go b/vendor/github.com/gorilla/websocket/json.go index 4f0e368..dc2c1f6 100644 --- a/vendor/github.com/gorilla/websocket/json.go +++ b/vendor/github.com/gorilla/websocket/json.go @@ -9,12 +9,14 @@ import ( "io" ) -// WriteJSON is deprecated, use c.WriteJSON instead. +// WriteJSON writes the JSON encoding of v as a message. +// +// Deprecated: Use c.WriteJSON instead. func WriteJSON(c *Conn, v interface{}) error { return c.WriteJSON(v) } -// WriteJSON writes the JSON encoding of v to the connection. +// WriteJSON writes the JSON encoding of v as a message. // // See the documentation for encoding/json Marshal for details about the // conversion of Go values to JSON. @@ -31,7 +33,10 @@ func (c *Conn) WriteJSON(v interface{}) error { return err2 } -// ReadJSON is deprecated, use c.ReadJSON instead. +// ReadJSON reads the next JSON-encoded message from the connection and stores +// it in the value pointed to by v. +// +// Deprecated: Use c.ReadJSON instead. func ReadJSON(c *Conn, v interface{}) error { return c.ReadJSON(v) } diff --git a/vendor/github.com/gorilla/websocket/mask.go b/vendor/github.com/gorilla/websocket/mask.go index 6a88bbc..577fce9 100644 --- a/vendor/github.com/gorilla/websocket/mask.go +++ b/vendor/github.com/gorilla/websocket/mask.go @@ -11,7 +11,6 @@ import "unsafe" const wordSize = int(unsafe.Sizeof(uintptr(0))) func maskBytes(key [4]byte, pos int, b []byte) int { - // Mask one byte at a time for small buffers. if len(b) < 2*wordSize { for i := range b { diff --git a/vendor/github.com/gorilla/websocket/prepared.go b/vendor/github.com/gorilla/websocket/prepared.go index 1efffbd..c854225 100644 --- a/vendor/github.com/gorilla/websocket/prepared.go +++ b/vendor/github.com/gorilla/websocket/prepared.go @@ -19,7 +19,6 @@ import ( type PreparedMessage struct { messageType int data []byte - err error mu sync.Mutex frames map[prepareKey]*preparedFrame } @@ -74,8 +73,8 @@ func (pm *PreparedMessage) frame(key prepareKey) (int, []byte, error) { // Prepare a frame using a 'fake' connection. // TODO: Refactor code in conn.go to allow more direct construction of // the frame. - mu := make(chan bool, 1) - mu <- true + mu := make(chan struct{}, 1) + mu <- struct{}{} var nc prepareConn c := &Conn{ conn: &nc, diff --git a/vendor/github.com/gorilla/websocket/proxy.go b/vendor/github.com/gorilla/websocket/proxy.go new file mode 100644 index 0000000..e87a8c9 --- /dev/null +++ b/vendor/github.com/gorilla/websocket/proxy.go @@ -0,0 +1,77 @@ +// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +import ( + "bufio" + "encoding/base64" + "errors" + "net" + "net/http" + "net/url" + "strings" +) + +type netDialerFunc func(network, addr string) (net.Conn, error) + +func (fn netDialerFunc) Dial(network, addr string) (net.Conn, error) { + return fn(network, addr) +} + +func init() { + proxy_RegisterDialerType("http", func(proxyURL *url.URL, forwardDialer proxy_Dialer) (proxy_Dialer, error) { + return &httpProxyDialer{proxyURL: proxyURL, forwardDial: forwardDialer.Dial}, nil + }) +} + +type httpProxyDialer struct { + proxyURL *url.URL + forwardDial func(network, addr string) (net.Conn, error) +} + +func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error) { + hostPort, _ := hostPortNoPort(hpd.proxyURL) + conn, err := hpd.forwardDial(network, hostPort) + if err != nil { + return nil, err + } + + connectHeader := make(http.Header) + if user := hpd.proxyURL.User; user != nil { + proxyUser := user.Username() + if proxyPassword, passwordSet := user.Password(); passwordSet { + credential := base64.StdEncoding.EncodeToString([]byte(proxyUser + ":" + proxyPassword)) + connectHeader.Set("Proxy-Authorization", "Basic "+credential) + } + } + + connectReq := &http.Request{ + Method: "CONNECT", + URL: &url.URL{Opaque: addr}, + Host: addr, + Header: connectHeader, + } + + if err := connectReq.Write(conn); err != nil { + conn.Close() + return nil, err + } + + // Read response. It's OK to use and discard buffered reader here becaue + // the remote server does not speak until spoken to. + br := bufio.NewReader(conn) + resp, err := http.ReadResponse(br, connectReq) + if err != nil { + conn.Close() + return nil, err + } + + if resp.StatusCode != 200 { + conn.Close() + f := strings.SplitN(resp.Status, " ", 2) + return nil, errors.New(f[1]) + } + return conn, nil +} diff --git a/vendor/github.com/gorilla/websocket/server.go b/vendor/github.com/gorilla/websocket/server.go index 3495e0f..887d558 100644 --- a/vendor/github.com/gorilla/websocket/server.go +++ b/vendor/github.com/gorilla/websocket/server.go @@ -7,7 +7,7 @@ package websocket import ( "bufio" "errors" - "net" + "io" "net/http" "net/url" "strings" @@ -27,16 +27,29 @@ type Upgrader struct { // HandshakeTimeout specifies the duration for the handshake to complete. HandshakeTimeout time.Duration - // ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer + // ReadBufferSize and WriteBufferSize specify I/O buffer sizes in bytes. If a buffer // size is zero, then buffers allocated by the HTTP server are used. The // I/O buffer sizes do not limit the size of the messages that can be sent // or received. ReadBufferSize, WriteBufferSize int + // WriteBufferPool is a pool of buffers for write operations. If the value + // is not set, then write buffers are allocated to the connection for the + // lifetime of the connection. + // + // A pool is most useful when the application has a modest volume of writes + // across a large number of connections. + // + // Applications should use a single pool for each unique value of + // WriteBufferSize. + WriteBufferPool BufferPool + // Subprotocols specifies the server's supported protocols in order of - // preference. If this field is set, then the Upgrade method negotiates a + // preference. If this field is not nil, then the Upgrade method negotiates a // subprotocol by selecting the first match in this list with a protocol - // requested by the client. + // requested by the client. If there's no match, then no protocol is + // negotiated (the Sec-Websocket-Protocol header is not included in the + // handshake response). Subprotocols []string // Error specifies the function for generating HTTP error responses. If Error @@ -44,8 +57,12 @@ type Upgrader struct { Error func(w http.ResponseWriter, r *http.Request, status int, reason error) // CheckOrigin returns true if the request Origin header is acceptable. If - // CheckOrigin is nil, the host in the Origin header must not be set or - // must match the host of the request. + // CheckOrigin is nil, then a safe default is used: return false if the + // Origin request header is present and the origin host is not equal to + // request Host header. + // + // A CheckOrigin function should carefully validate the request origin to + // prevent cross-site request forgery. CheckOrigin func(r *http.Request) bool // EnableCompression specify if the server should attempt to negotiate per @@ -76,7 +93,7 @@ func checkSameOrigin(r *http.Request) bool { if err != nil { return false } - return u.Host == r.Host + return equalASCIIFold(u.Host, r.Host) } func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string { @@ -99,42 +116,44 @@ func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header // // The responseHeader is included in the response to the client's upgrade // request. Use the responseHeader to specify cookies (Set-Cookie) and the -// application negotiated subprotocol (Sec-Websocket-Protocol). +// application negotiated subprotocol (Sec-WebSocket-Protocol). // // If the upgrade fails, then Upgrade replies to the client with an HTTP error // response. func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) { - if r.Method != "GET" { - return u.returnError(w, r, http.StatusMethodNotAllowed, "websocket: not a websocket handshake: request method is not GET") - } - - if _, ok := responseHeader["Sec-Websocket-Extensions"]; ok { - return u.returnError(w, r, http.StatusInternalServerError, "websocket: application specific 'Sec-Websocket-Extensions' headers are unsupported") - } + const badHandshake = "websocket: the client is not using the websocket protocol: " if !tokenListContainsValue(r.Header, "Connection", "upgrade") { - return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'upgrade' token not found in 'Connection' header") + return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'upgrade' token not found in 'Connection' header") } if !tokenListContainsValue(r.Header, "Upgrade", "websocket") { - return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'websocket' token not found in 'Upgrade' header") + return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'websocket' token not found in 'Upgrade' header") + } + + if r.Method != "GET" { + return u.returnError(w, r, http.StatusMethodNotAllowed, badHandshake+"request method is not GET") } if !tokenListContainsValue(r.Header, "Sec-Websocket-Version", "13") { return u.returnError(w, r, http.StatusBadRequest, "websocket: unsupported version: 13 not found in 'Sec-Websocket-Version' header") } + if _, ok := responseHeader["Sec-Websocket-Extensions"]; ok { + return u.returnError(w, r, http.StatusInternalServerError, "websocket: application specific 'Sec-WebSocket-Extensions' headers are unsupported") + } + checkOrigin := u.CheckOrigin if checkOrigin == nil { checkOrigin = checkSameOrigin } if !checkOrigin(r) { - return u.returnError(w, r, http.StatusForbidden, "websocket: 'Origin' header value not allowed") + return u.returnError(w, r, http.StatusForbidden, "websocket: request origin not allowed by Upgrader.CheckOrigin") } challengeKey := r.Header.Get("Sec-Websocket-Key") if challengeKey == "" { - return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: `Sec-Websocket-Key' header is missing or blank") + return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'Sec-WebSocket-Key' header is missing or blank") } subprotocol := u.selectSubprotocol(r, responseHeader) @@ -151,17 +170,12 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade } } - var ( - netConn net.Conn - err error - ) - h, ok := w.(http.Hijacker) if !ok { return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker") } var brw *bufio.ReadWriter - netConn, brw, err = h.Hijack() + netConn, brw, err := h.Hijack() if err != nil { return u.returnError(w, r, http.StatusInternalServerError, err.Error()) } @@ -171,7 +185,21 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade return nil, errors.New("websocket: client sent data before handshake is complete") } - c := newConnBRW(netConn, true, u.ReadBufferSize, u.WriteBufferSize, brw) + var br *bufio.Reader + if u.ReadBufferSize == 0 && bufioReaderSize(netConn, brw.Reader) > 256 { + // Reuse hijacked buffered reader as connection reader. + br = brw.Reader + } + + buf := bufioWriterBuffer(netConn, brw.Writer) + + var writeBuf []byte + if u.WriteBufferPool == nil && u.WriteBufferSize == 0 && len(buf) >= maxFrameHeaderSize+256 { + // Reuse hijacked write buffer as connection buffer. + writeBuf = buf + } + + c := newConn(netConn, true, u.ReadBufferSize, u.WriteBufferSize, u.WriteBufferPool, br, writeBuf) c.subprotocol = subprotocol if compress { @@ -179,17 +207,23 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade c.newDecompressionReader = decompressNoContextTakeover } - p := c.writeBuf[:0] + // Use larger of hijacked buffer and connection write buffer for header. + p := buf + if len(c.writeBuf) > len(p) { + p = c.writeBuf + } + p = p[:0] + p = append(p, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...) p = append(p, computeAcceptKey(challengeKey)...) p = append(p, "\r\n"...) if c.subprotocol != "" { - p = append(p, "Sec-Websocket-Protocol: "...) + p = append(p, "Sec-WebSocket-Protocol: "...) p = append(p, c.subprotocol...) p = append(p, "\r\n"...) } if compress { - p = append(p, "Sec-Websocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover\r\n"...) + p = append(p, "Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover\r\n"...) } for k, vs := range responseHeader { if k == "Sec-Websocket-Protocol" { @@ -230,13 +264,14 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade // Upgrade upgrades the HTTP server connection to the WebSocket protocol. // -// This function is deprecated, use websocket.Upgrader instead. +// Deprecated: Use websocket.Upgrader instead. // -// The application is responsible for checking the request origin before -// calling Upgrade. An example implementation of the same origin policy is: +// Upgrade does not perform origin checking. The application is responsible for +// checking the Origin header before calling Upgrade. An example implementation +// of the same origin policy check is: // // if req.Header.Get("Origin") != "http://"+req.Host { -// http.Error(w, "Origin not allowed", 403) +// http.Error(w, "Origin not allowed", http.StatusForbidden) // return // } // @@ -289,3 +324,40 @@ func IsWebSocketUpgrade(r *http.Request) bool { return tokenListContainsValue(r.Header, "Connection", "upgrade") && tokenListContainsValue(r.Header, "Upgrade", "websocket") } + +// bufioReaderSize size returns the size of a bufio.Reader. +func bufioReaderSize(originalReader io.Reader, br *bufio.Reader) int { + // This code assumes that peek on a reset reader returns + // bufio.Reader.buf[:0]. + // TODO: Use bufio.Reader.Size() after Go 1.10 + br.Reset(originalReader) + if p, err := br.Peek(0); err == nil { + return cap(p) + } + return 0 +} + +// writeHook is an io.Writer that records the last slice passed to it vio +// io.Writer.Write. +type writeHook struct { + p []byte +} + +func (wh *writeHook) Write(p []byte) (int, error) { + wh.p = p + return len(p), nil +} + +// bufioWriterBuffer grabs the buffer from a bufio.Writer. +func bufioWriterBuffer(originalWriter io.Writer, bw *bufio.Writer) []byte { + // This code assumes that bufio.Writer.buf[:1] is passed to the + // bufio.Writer's underlying writer. + var wh writeHook + bw.Reset(&wh) + bw.WriteByte(0) + bw.Flush() + + bw.Reset(originalWriter) + + return wh.p[:cap(wh.p)] +} diff --git a/vendor/github.com/gorilla/websocket/trace.go b/vendor/github.com/gorilla/websocket/trace.go new file mode 100644 index 0000000..834f122 --- /dev/null +++ b/vendor/github.com/gorilla/websocket/trace.go @@ -0,0 +1,19 @@ +// +build go1.8 + +package websocket + +import ( + "crypto/tls" + "net/http/httptrace" +) + +func doHandshakeWithTrace(trace *httptrace.ClientTrace, tlsConn *tls.Conn, cfg *tls.Config) error { + if trace.TLSHandshakeStart != nil { + trace.TLSHandshakeStart() + } + err := doHandshake(tlsConn, cfg) + if trace.TLSHandshakeDone != nil { + trace.TLSHandshakeDone(tlsConn.ConnectionState(), err) + } + return err +} diff --git a/vendor/github.com/gorilla/websocket/trace_17.go b/vendor/github.com/gorilla/websocket/trace_17.go new file mode 100644 index 0000000..77d05a0 --- /dev/null +++ b/vendor/github.com/gorilla/websocket/trace_17.go @@ -0,0 +1,12 @@ +// +build !go1.8 + +package websocket + +import ( + "crypto/tls" + "net/http/httptrace" +) + +func doHandshakeWithTrace(trace *httptrace.ClientTrace, tlsConn *tls.Conn, cfg *tls.Config) error { + return doHandshake(tlsConn, cfg) +} diff --git a/vendor/github.com/gorilla/websocket/util.go b/vendor/github.com/gorilla/websocket/util.go index 9a4908d..7bf2f66 100644 --- a/vendor/github.com/gorilla/websocket/util.go +++ b/vendor/github.com/gorilla/websocket/util.go @@ -11,6 +11,7 @@ import ( "io" "net/http" "strings" + "unicode/utf8" ) var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11") @@ -30,68 +31,113 @@ func generateChallengeKey() (string, error) { return base64.StdEncoding.EncodeToString(p), nil } -// Octet types from RFC 2616. -var octetTypes [256]byte - -const ( - isTokenOctet = 1 << iota - isSpaceOctet -) - -func init() { - // From RFC 2616 - // - // OCTET = - // CHAR = - // CTL = - // CR = - // LF = - // SP = - // HT = - // <"> = - // CRLF = CR LF - // LWS = [CRLF] 1*( SP | HT ) - // TEXT = - // separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> - // | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT - // token = 1* - // qdtext = > - - for c := 0; c < 256; c++ { - var t byte - isCtl := c <= 31 || c == 127 - isChar := 0 <= c && c <= 127 - isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0 - if strings.IndexRune(" \t\r\n", rune(c)) >= 0 { - t |= isSpaceOctet - } - if isChar && !isCtl && !isSeparator { - t |= isTokenOctet - } - octetTypes[c] = t - } +// Token octets per RFC 2616. +var isTokenOctet = [256]bool{ + '!': true, + '#': true, + '$': true, + '%': true, + '&': true, + '\'': true, + '*': true, + '+': true, + '-': true, + '.': true, + '0': true, + '1': true, + '2': true, + '3': true, + '4': true, + '5': true, + '6': true, + '7': true, + '8': true, + '9': true, + 'A': true, + 'B': true, + 'C': true, + 'D': true, + 'E': true, + 'F': true, + 'G': true, + 'H': true, + 'I': true, + 'J': true, + 'K': true, + 'L': true, + 'M': true, + 'N': true, + 'O': true, + 'P': true, + 'Q': true, + 'R': true, + 'S': true, + 'T': true, + 'U': true, + 'W': true, + 'V': true, + 'X': true, + 'Y': true, + 'Z': true, + '^': true, + '_': true, + '`': true, + 'a': true, + 'b': true, + 'c': true, + 'd': true, + 'e': true, + 'f': true, + 'g': true, + 'h': true, + 'i': true, + 'j': true, + 'k': true, + 'l': true, + 'm': true, + 'n': true, + 'o': true, + 'p': true, + 'q': true, + 'r': true, + 's': true, + 't': true, + 'u': true, + 'v': true, + 'w': true, + 'x': true, + 'y': true, + 'z': true, + '|': true, + '~': true, } +// skipSpace returns a slice of the string s with all leading RFC 2616 linear +// whitespace removed. func skipSpace(s string) (rest string) { i := 0 for ; i < len(s); i++ { - if octetTypes[s[i]]&isSpaceOctet == 0 { + if b := s[i]; b != ' ' && b != '\t' { break } } return s[i:] } +// nextToken returns the leading RFC 2616 token of s and the string following +// the token. func nextToken(s string) (token, rest string) { i := 0 for ; i < len(s); i++ { - if octetTypes[s[i]]&isTokenOctet == 0 { + if !isTokenOctet[s[i]] { break } } return s[:i], s[i:] } +// nextTokenOrQuoted returns the leading token or quoted string per RFC 2616 +// and the string following the token or quoted string. func nextTokenOrQuoted(s string) (value string, rest string) { if !strings.HasPrefix(s, "\"") { return nextToken(s) @@ -111,14 +157,14 @@ func nextTokenOrQuoted(s string) (value string, rest string) { case escape: escape = false p[j] = b - j += 1 + j++ case b == '\\': escape = true case b == '"': return string(p[:j]), s[i+1:] default: p[j] = b - j += 1 + j++ } } return "", "" @@ -127,8 +173,32 @@ func nextTokenOrQuoted(s string) (value string, rest string) { return "", "" } +// equalASCIIFold returns true if s is equal to t with ASCII case folding as +// defined in RFC 4790. +func equalASCIIFold(s, t string) bool { + for s != "" && t != "" { + sr, size := utf8.DecodeRuneInString(s) + s = s[size:] + tr, size := utf8.DecodeRuneInString(t) + t = t[size:] + if sr == tr { + continue + } + if 'A' <= sr && sr <= 'Z' { + sr = sr + 'a' - 'A' + } + if 'A' <= tr && tr <= 'Z' { + tr = tr + 'a' - 'A' + } + if sr != tr { + return false + } + } + return s == t +} + // tokenListContainsValue returns true if the 1#token header with the given -// name contains token. +// name contains a token equal to value with ASCII case folding. func tokenListContainsValue(header http.Header, name string, value string) bool { headers: for _, s := range header[name] { @@ -142,7 +212,7 @@ headers: if s != "" && s[0] != ',' { continue headers } - if strings.EqualFold(t, value) { + if equalASCIIFold(t, value) { return true } if s == "" { @@ -154,9 +224,8 @@ headers: return false } -// parseExtensiosn parses WebSocket extensions from a header. +// parseExtensions parses WebSocket extensions from a header. func parseExtensions(header http.Header) []map[string]string { - // From RFC 6455: // // Sec-WebSocket-Extensions = extension-list diff --git a/vendor/github.com/gorilla/websocket/x_net_proxy.go b/vendor/github.com/gorilla/websocket/x_net_proxy.go new file mode 100644 index 0000000..2e668f6 --- /dev/null +++ b/vendor/github.com/gorilla/websocket/x_net_proxy.go @@ -0,0 +1,473 @@ +// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT. +//go:generate bundle -o x_net_proxy.go golang.org/x/net/proxy + +// Package proxy provides support for a variety of protocols to proxy network +// data. +// + +package websocket + +import ( + "errors" + "io" + "net" + "net/url" + "os" + "strconv" + "strings" + "sync" +) + +type proxy_direct struct{} + +// Direct is a direct proxy: one that makes network connections directly. +var proxy_Direct = proxy_direct{} + +func (proxy_direct) Dial(network, addr string) (net.Conn, error) { + return net.Dial(network, addr) +} + +// A PerHost directs connections to a default Dialer unless the host name +// requested matches one of a number of exceptions. +type proxy_PerHost struct { + def, bypass proxy_Dialer + + bypassNetworks []*net.IPNet + bypassIPs []net.IP + bypassZones []string + bypassHosts []string +} + +// NewPerHost returns a PerHost Dialer that directs connections to either +// defaultDialer or bypass, depending on whether the connection matches one of +// the configured rules. +func proxy_NewPerHost(defaultDialer, bypass proxy_Dialer) *proxy_PerHost { + return &proxy_PerHost{ + def: defaultDialer, + bypass: bypass, + } +} + +// Dial connects to the address addr on the given network through either +// defaultDialer or bypass. +func (p *proxy_PerHost) Dial(network, addr string) (c net.Conn, err error) { + host, _, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + + return p.dialerForRequest(host).Dial(network, addr) +} + +func (p *proxy_PerHost) dialerForRequest(host string) proxy_Dialer { + if ip := net.ParseIP(host); ip != nil { + for _, net := range p.bypassNetworks { + if net.Contains(ip) { + return p.bypass + } + } + for _, bypassIP := range p.bypassIPs { + if bypassIP.Equal(ip) { + return p.bypass + } + } + return p.def + } + + for _, zone := range p.bypassZones { + if strings.HasSuffix(host, zone) { + return p.bypass + } + if host == zone[1:] { + // For a zone ".example.com", we match "example.com" + // too. + return p.bypass + } + } + for _, bypassHost := range p.bypassHosts { + if bypassHost == host { + return p.bypass + } + } + return p.def +} + +// AddFromString parses a string that contains comma-separated values +// specifying hosts that should use the bypass proxy. Each value is either an +// IP address, a CIDR range, a zone (*.example.com) or a host name +// (localhost). A best effort is made to parse the string and errors are +// ignored. +func (p *proxy_PerHost) AddFromString(s string) { + hosts := strings.Split(s, ",") + for _, host := range hosts { + host = strings.TrimSpace(host) + if len(host) == 0 { + continue + } + if strings.Contains(host, "/") { + // We assume that it's a CIDR address like 127.0.0.0/8 + if _, net, err := net.ParseCIDR(host); err == nil { + p.AddNetwork(net) + } + continue + } + if ip := net.ParseIP(host); ip != nil { + p.AddIP(ip) + continue + } + if strings.HasPrefix(host, "*.") { + p.AddZone(host[1:]) + continue + } + p.AddHost(host) + } +} + +// AddIP specifies an IP address that will use the bypass proxy. Note that +// this will only take effect if a literal IP address is dialed. A connection +// to a named host will never match an IP. +func (p *proxy_PerHost) AddIP(ip net.IP) { + p.bypassIPs = append(p.bypassIPs, ip) +} + +// AddNetwork specifies an IP range that will use the bypass proxy. Note that +// this will only take effect if a literal IP address is dialed. A connection +// to a named host will never match. +func (p *proxy_PerHost) AddNetwork(net *net.IPNet) { + p.bypassNetworks = append(p.bypassNetworks, net) +} + +// AddZone specifies a DNS suffix that will use the bypass proxy. A zone of +// "example.com" matches "example.com" and all of its subdomains. +func (p *proxy_PerHost) AddZone(zone string) { + if strings.HasSuffix(zone, ".") { + zone = zone[:len(zone)-1] + } + if !strings.HasPrefix(zone, ".") { + zone = "." + zone + } + p.bypassZones = append(p.bypassZones, zone) +} + +// AddHost specifies a host name that will use the bypass proxy. +func (p *proxy_PerHost) AddHost(host string) { + if strings.HasSuffix(host, ".") { + host = host[:len(host)-1] + } + p.bypassHosts = append(p.bypassHosts, host) +} + +// A Dialer is a means to establish a connection. +type proxy_Dialer interface { + // Dial connects to the given address via the proxy. + Dial(network, addr string) (c net.Conn, err error) +} + +// Auth contains authentication parameters that specific Dialers may require. +type proxy_Auth struct { + User, Password string +} + +// FromEnvironment returns the dialer specified by the proxy related variables in +// the environment. +func proxy_FromEnvironment() proxy_Dialer { + allProxy := proxy_allProxyEnv.Get() + if len(allProxy) == 0 { + return proxy_Direct + } + + proxyURL, err := url.Parse(allProxy) + if err != nil { + return proxy_Direct + } + proxy, err := proxy_FromURL(proxyURL, proxy_Direct) + if err != nil { + return proxy_Direct + } + + noProxy := proxy_noProxyEnv.Get() + if len(noProxy) == 0 { + return proxy + } + + perHost := proxy_NewPerHost(proxy, proxy_Direct) + perHost.AddFromString(noProxy) + return perHost +} + +// proxySchemes is a map from URL schemes to a function that creates a Dialer +// from a URL with such a scheme. +var proxy_proxySchemes map[string]func(*url.URL, proxy_Dialer) (proxy_Dialer, error) + +// RegisterDialerType takes a URL scheme and a function to generate Dialers from +// a URL with that scheme and a forwarding Dialer. Registered schemes are used +// by FromURL. +func proxy_RegisterDialerType(scheme string, f func(*url.URL, proxy_Dialer) (proxy_Dialer, error)) { + if proxy_proxySchemes == nil { + proxy_proxySchemes = make(map[string]func(*url.URL, proxy_Dialer) (proxy_Dialer, error)) + } + proxy_proxySchemes[scheme] = f +} + +// FromURL returns a Dialer given a URL specification and an underlying +// Dialer for it to make network requests. +func proxy_FromURL(u *url.URL, forward proxy_Dialer) (proxy_Dialer, error) { + var auth *proxy_Auth + if u.User != nil { + auth = new(proxy_Auth) + auth.User = u.User.Username() + if p, ok := u.User.Password(); ok { + auth.Password = p + } + } + + switch u.Scheme { + case "socks5": + return proxy_SOCKS5("tcp", u.Host, auth, forward) + } + + // If the scheme doesn't match any of the built-in schemes, see if it + // was registered by another package. + if proxy_proxySchemes != nil { + if f, ok := proxy_proxySchemes[u.Scheme]; ok { + return f(u, forward) + } + } + + return nil, errors.New("proxy: unknown scheme: " + u.Scheme) +} + +var ( + proxy_allProxyEnv = &proxy_envOnce{ + names: []string{"ALL_PROXY", "all_proxy"}, + } + proxy_noProxyEnv = &proxy_envOnce{ + names: []string{"NO_PROXY", "no_proxy"}, + } +) + +// envOnce looks up an environment variable (optionally by multiple +// names) once. It mitigates expensive lookups on some platforms +// (e.g. Windows). +// (Borrowed from net/http/transport.go) +type proxy_envOnce struct { + names []string + once sync.Once + val string +} + +func (e *proxy_envOnce) Get() string { + e.once.Do(e.init) + return e.val +} + +func (e *proxy_envOnce) init() { + for _, n := range e.names { + e.val = os.Getenv(n) + if e.val != "" { + return + } + } +} + +// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address +// with an optional username and password. See RFC 1928 and RFC 1929. +func proxy_SOCKS5(network, addr string, auth *proxy_Auth, forward proxy_Dialer) (proxy_Dialer, error) { + s := &proxy_socks5{ + network: network, + addr: addr, + forward: forward, + } + if auth != nil { + s.user = auth.User + s.password = auth.Password + } + + return s, nil +} + +type proxy_socks5 struct { + user, password string + network, addr string + forward proxy_Dialer +} + +const proxy_socks5Version = 5 + +const ( + proxy_socks5AuthNone = 0 + proxy_socks5AuthPassword = 2 +) + +const proxy_socks5Connect = 1 + +const ( + proxy_socks5IP4 = 1 + proxy_socks5Domain = 3 + proxy_socks5IP6 = 4 +) + +var proxy_socks5Errors = []string{ + "", + "general failure", + "connection forbidden", + "network unreachable", + "host unreachable", + "connection refused", + "TTL expired", + "command not supported", + "address type not supported", +} + +// Dial connects to the address addr on the given network via the SOCKS5 proxy. +func (s *proxy_socks5) Dial(network, addr string) (net.Conn, error) { + switch network { + case "tcp", "tcp6", "tcp4": + default: + return nil, errors.New("proxy: no support for SOCKS5 proxy connections of type " + network) + } + + conn, err := s.forward.Dial(s.network, s.addr) + if err != nil { + return nil, err + } + if err := s.connect(conn, addr); err != nil { + conn.Close() + return nil, err + } + return conn, nil +} + +// connect takes an existing connection to a socks5 proxy server, +// and commands the server to extend that connection to target, +// which must be a canonical address with a host and port. +func (s *proxy_socks5) connect(conn net.Conn, target string) error { + host, portStr, err := net.SplitHostPort(target) + if err != nil { + return err + } + + port, err := strconv.Atoi(portStr) + if err != nil { + return errors.New("proxy: failed to parse port number: " + portStr) + } + if port < 1 || port > 0xffff { + return errors.New("proxy: port number out of range: " + portStr) + } + + // the size here is just an estimate + buf := make([]byte, 0, 6+len(host)) + + buf = append(buf, proxy_socks5Version) + if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 { + buf = append(buf, 2 /* num auth methods */, proxy_socks5AuthNone, proxy_socks5AuthPassword) + } else { + buf = append(buf, 1 /* num auth methods */, proxy_socks5AuthNone) + } + + if _, err := conn.Write(buf); err != nil { + return errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + + if _, err := io.ReadFull(conn, buf[:2]); err != nil { + return errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + if buf[0] != 5 { + return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0]))) + } + if buf[1] == 0xff { + return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication") + } + + // See RFC 1929 + if buf[1] == proxy_socks5AuthPassword { + buf = buf[:0] + buf = append(buf, 1 /* password protocol version */) + buf = append(buf, uint8(len(s.user))) + buf = append(buf, s.user...) + buf = append(buf, uint8(len(s.password))) + buf = append(buf, s.password...) + + if _, err := conn.Write(buf); err != nil { + return errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + + if _, err := io.ReadFull(conn, buf[:2]); err != nil { + return errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + + if buf[1] != 0 { + return errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password") + } + } + + buf = buf[:0] + buf = append(buf, proxy_socks5Version, proxy_socks5Connect, 0 /* reserved */) + + if ip := net.ParseIP(host); ip != nil { + if ip4 := ip.To4(); ip4 != nil { + buf = append(buf, proxy_socks5IP4) + ip = ip4 + } else { + buf = append(buf, proxy_socks5IP6) + } + buf = append(buf, ip...) + } else { + if len(host) > 255 { + return errors.New("proxy: destination host name too long: " + host) + } + buf = append(buf, proxy_socks5Domain) + buf = append(buf, byte(len(host))) + buf = append(buf, host...) + } + buf = append(buf, byte(port>>8), byte(port)) + + if _, err := conn.Write(buf); err != nil { + return errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + + if _, err := io.ReadFull(conn, buf[:4]); err != nil { + return errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + + failure := "unknown error" + if int(buf[1]) < len(proxy_socks5Errors) { + failure = proxy_socks5Errors[buf[1]] + } + + if len(failure) > 0 { + return errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure) + } + + bytesToDiscard := 0 + switch buf[3] { + case proxy_socks5IP4: + bytesToDiscard = net.IPv4len + case proxy_socks5IP6: + bytesToDiscard = net.IPv6len + case proxy_socks5Domain: + _, err := io.ReadFull(conn, buf[:1]) + if err != nil { + return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + bytesToDiscard = int(buf[0]) + default: + return errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr) + } + + if cap(buf) < bytesToDiscard { + buf = make([]byte, bytesToDiscard) + } else { + buf = buf[:bytesToDiscard] + } + if _, err := io.ReadFull(conn, buf); err != nil { + return errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + + // Also need to discard the port number + if _, err := io.ReadFull(conn, buf[:2]); err != nil { + return errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + + return nil +} diff --git a/vendor/github.com/pkg/errors/.gitignore b/vendor/github.com/pkg/errors/.gitignore deleted file mode 100644 index daf913b..0000000 --- a/vendor/github.com/pkg/errors/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so - -# Folders -_obj -_test - -# Architecture specific extensions/prefixes -*.[568vq] -[568vq].out - -*.cgo1.go -*.cgo2.c -_cgo_defun.c -_cgo_gotypes.go -_cgo_export.* - -_testmain.go - -*.exe -*.test -*.prof diff --git a/vendor/github.com/pkg/errors/.travis.yml b/vendor/github.com/pkg/errors/.travis.yml deleted file mode 100644 index 588ceca..0000000 --- a/vendor/github.com/pkg/errors/.travis.yml +++ /dev/null @@ -1,11 +0,0 @@ -language: go -go_import_path: github.com/pkg/errors -go: - - 1.4.3 - - 1.5.4 - - 1.6.2 - - 1.7.1 - - tip - -script: - - go test -v ./... diff --git a/vendor/github.com/pkg/errors/LICENSE b/vendor/github.com/pkg/errors/LICENSE deleted file mode 100644 index 835ba3e..0000000 --- a/vendor/github.com/pkg/errors/LICENSE +++ /dev/null @@ -1,23 +0,0 @@ -Copyright (c) 2015, Dave Cheney -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/pkg/errors/README.md b/vendor/github.com/pkg/errors/README.md deleted file mode 100644 index 273db3c..0000000 --- a/vendor/github.com/pkg/errors/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) - -Package errors provides simple error handling primitives. - -`go get github.com/pkg/errors` - -The traditional error handling idiom in Go is roughly akin to -```go -if err != nil { - return err -} -``` -which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error. - -## Adding context to an error - -The errors.Wrap function returns a new error that adds context to the original error. For example -```go -_, err := ioutil.ReadAll(r) -if err != nil { - return errors.Wrap(err, "read failed") -} -``` -## Retrieving the cause of an error - -Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`. -```go -type causer interface { - Cause() error -} -``` -`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example: -```go -switch err := errors.Cause(err).(type) { -case *MyError: - // handle specifically -default: - // unknown error -} -``` - -[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors). - -## Contributing - -We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high. - -Before proposing a change, please discuss your change by raising an issue. - -## Licence - -BSD-2-Clause diff --git a/vendor/github.com/pkg/errors/appveyor.yml b/vendor/github.com/pkg/errors/appveyor.yml deleted file mode 100644 index a932ead..0000000 --- a/vendor/github.com/pkg/errors/appveyor.yml +++ /dev/null @@ -1,32 +0,0 @@ -version: build-{build}.{branch} - -clone_folder: C:\gopath\src\github.com\pkg\errors -shallow_clone: true # for startup speed - -environment: - GOPATH: C:\gopath - -platform: - - x64 - -# http://www.appveyor.com/docs/installed-software -install: - # some helpful output for debugging builds - - go version - - go env - # pre-installed MinGW at C:\MinGW is 32bit only - # but MSYS2 at C:\msys64 has mingw64 - - set PATH=C:\msys64\mingw64\bin;%PATH% - - gcc --version - - g++ --version - -build_script: - - go install -v ./... - -test_script: - - set PATH=C:\gopath\bin;%PATH% - - go test -v ./... - -#artifacts: -# - path: '%GOPATH%\bin\*.exe' -deploy: off diff --git a/vendor/github.com/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go deleted file mode 100644 index 842ee80..0000000 --- a/vendor/github.com/pkg/errors/errors.go +++ /dev/null @@ -1,269 +0,0 @@ -// Package errors provides simple error handling primitives. -// -// The traditional error handling idiom in Go is roughly akin to -// -// if err != nil { -// return err -// } -// -// which applied recursively up the call stack results in error reports -// without context or debugging information. The errors package allows -// programmers to add context to the failure path in their code in a way -// that does not destroy the original value of the error. -// -// Adding context to an error -// -// The errors.Wrap function returns a new error that adds context to the -// original error by recording a stack trace at the point Wrap is called, -// and the supplied message. For example -// -// _, err := ioutil.ReadAll(r) -// if err != nil { -// return errors.Wrap(err, "read failed") -// } -// -// If additional control is required the errors.WithStack and errors.WithMessage -// functions destructure errors.Wrap into its component operations of annotating -// an error with a stack trace and an a message, respectively. -// -// Retrieving the cause of an error -// -// Using errors.Wrap constructs a stack of errors, adding context to the -// preceding error. Depending on the nature of the error it may be necessary -// to reverse the operation of errors.Wrap to retrieve the original error -// for inspection. Any error value which implements this interface -// -// type causer interface { -// Cause() error -// } -// -// can be inspected by errors.Cause. errors.Cause will recursively retrieve -// the topmost error which does not implement causer, which is assumed to be -// the original cause. For example: -// -// switch err := errors.Cause(err).(type) { -// case *MyError: -// // handle specifically -// default: -// // unknown error -// } -// -// causer interface is not exported by this package, but is considered a part -// of stable public API. -// -// Formatted printing of errors -// -// All error values returned from this package implement fmt.Formatter and can -// be formatted by the fmt package. The following verbs are supported -// -// %s print the error. If the error has a Cause it will be -// printed recursively -// %v see %s -// %+v extended format. Each Frame of the error's StackTrace will -// be printed in detail. -// -// Retrieving the stack trace of an error or wrapper -// -// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are -// invoked. This information can be retrieved with the following interface. -// -// type stackTracer interface { -// StackTrace() errors.StackTrace -// } -// -// Where errors.StackTrace is defined as -// -// type StackTrace []Frame -// -// The Frame type represents a call site in the stack trace. Frame supports -// the fmt.Formatter interface that can be used for printing information about -// the stack trace of this error. For example: -// -// if err, ok := err.(stackTracer); ok { -// for _, f := range err.StackTrace() { -// fmt.Printf("%+s:%d", f) -// } -// } -// -// stackTracer interface is not exported by this package, but is considered a part -// of stable public API. -// -// See the documentation for Frame.Format for more details. -package errors - -import ( - "fmt" - "io" -) - -// New returns an error with the supplied message. -// New also records the stack trace at the point it was called. -func New(message string) error { - return &fundamental{ - msg: message, - stack: callers(), - } -} - -// Errorf formats according to a format specifier and returns the string -// as a value that satisfies error. -// Errorf also records the stack trace at the point it was called. -func Errorf(format string, args ...interface{}) error { - return &fundamental{ - msg: fmt.Sprintf(format, args...), - stack: callers(), - } -} - -// fundamental is an error that has a message and a stack, but no caller. -type fundamental struct { - msg string - *stack -} - -func (f *fundamental) Error() string { return f.msg } - -func (f *fundamental) Format(s fmt.State, verb rune) { - switch verb { - case 'v': - if s.Flag('+') { - io.WriteString(s, f.msg) - f.stack.Format(s, verb) - return - } - fallthrough - case 's': - io.WriteString(s, f.msg) - case 'q': - fmt.Fprintf(s, "%q", f.msg) - } -} - -// WithStack annotates err with a stack trace at the point WithStack was called. -// If err is nil, WithStack returns nil. -func WithStack(err error) error { - if err == nil { - return nil - } - return &withStack{ - err, - callers(), - } -} - -type withStack struct { - error - *stack -} - -func (w *withStack) Cause() error { return w.error } - -func (w *withStack) Format(s fmt.State, verb rune) { - switch verb { - case 'v': - if s.Flag('+') { - fmt.Fprintf(s, "%+v", w.Cause()) - w.stack.Format(s, verb) - return - } - fallthrough - case 's': - io.WriteString(s, w.Error()) - case 'q': - fmt.Fprintf(s, "%q", w.Error()) - } -} - -// Wrap returns an error annotating err with a stack trace -// at the point Wrap is called, and the supplied message. -// If err is nil, Wrap returns nil. -func Wrap(err error, message string) error { - if err == nil { - return nil - } - err = &withMessage{ - cause: err, - msg: message, - } - return &withStack{ - err, - callers(), - } -} - -// Wrapf returns an error annotating err with a stack trace -// at the point Wrapf is call, and the format specifier. -// If err is nil, Wrapf returns nil. -func Wrapf(err error, format string, args ...interface{}) error { - if err == nil { - return nil - } - err = &withMessage{ - cause: err, - msg: fmt.Sprintf(format, args...), - } - return &withStack{ - err, - callers(), - } -} - -// WithMessage annotates err with a new message. -// If err is nil, WithMessage returns nil. -func WithMessage(err error, message string) error { - if err == nil { - return nil - } - return &withMessage{ - cause: err, - msg: message, - } -} - -type withMessage struct { - cause error - msg string -} - -func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } -func (w *withMessage) Cause() error { return w.cause } - -func (w *withMessage) Format(s fmt.State, verb rune) { - switch verb { - case 'v': - if s.Flag('+') { - fmt.Fprintf(s, "%+v\n", w.Cause()) - io.WriteString(s, w.msg) - return - } - fallthrough - case 's', 'q': - io.WriteString(s, w.Error()) - } -} - -// Cause returns the underlying cause of the error, if possible. -// An error value has a cause if it implements the following -// interface: -// -// type causer interface { -// Cause() error -// } -// -// If the error does not implement Cause, the original error will -// be returned. If the error is nil, nil will be returned without further -// investigation. -func Cause(err error) error { - type causer interface { - Cause() error - } - - for err != nil { - cause, ok := err.(causer) - if !ok { - break - } - err = cause.Cause() - } - return err -} diff --git a/vendor/github.com/pkg/errors/stack.go b/vendor/github.com/pkg/errors/stack.go deleted file mode 100644 index 6b1f289..0000000 --- a/vendor/github.com/pkg/errors/stack.go +++ /dev/null @@ -1,178 +0,0 @@ -package errors - -import ( - "fmt" - "io" - "path" - "runtime" - "strings" -) - -// Frame represents a program counter inside a stack frame. -type Frame uintptr - -// pc returns the program counter for this frame; -// multiple frames may have the same PC value. -func (f Frame) pc() uintptr { return uintptr(f) - 1 } - -// file returns the full path to the file that contains the -// function for this Frame's pc. -func (f Frame) file() string { - fn := runtime.FuncForPC(f.pc()) - if fn == nil { - return "unknown" - } - file, _ := fn.FileLine(f.pc()) - return file -} - -// line returns the line number of source code of the -// function for this Frame's pc. -func (f Frame) line() int { - fn := runtime.FuncForPC(f.pc()) - if fn == nil { - return 0 - } - _, line := fn.FileLine(f.pc()) - return line -} - -// Format formats the frame according to the fmt.Formatter interface. -// -// %s source file -// %d source line -// %n function name -// %v equivalent to %s:%d -// -// Format accepts flags that alter the printing of some verbs, as follows: -// -// %+s path of source file relative to the compile time GOPATH -// %+v equivalent to %+s:%d -func (f Frame) Format(s fmt.State, verb rune) { - switch verb { - case 's': - switch { - case s.Flag('+'): - pc := f.pc() - fn := runtime.FuncForPC(pc) - if fn == nil { - io.WriteString(s, "unknown") - } else { - file, _ := fn.FileLine(pc) - fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) - } - default: - io.WriteString(s, path.Base(f.file())) - } - case 'd': - fmt.Fprintf(s, "%d", f.line()) - case 'n': - name := runtime.FuncForPC(f.pc()).Name() - io.WriteString(s, funcname(name)) - case 'v': - f.Format(s, 's') - io.WriteString(s, ":") - f.Format(s, 'd') - } -} - -// StackTrace is stack of Frames from innermost (newest) to outermost (oldest). -type StackTrace []Frame - -func (st StackTrace) Format(s fmt.State, verb rune) { - switch verb { - case 'v': - switch { - case s.Flag('+'): - for _, f := range st { - fmt.Fprintf(s, "\n%+v", f) - } - case s.Flag('#'): - fmt.Fprintf(s, "%#v", []Frame(st)) - default: - fmt.Fprintf(s, "%v", []Frame(st)) - } - case 's': - fmt.Fprintf(s, "%s", []Frame(st)) - } -} - -// stack represents a stack of program counters. -type stack []uintptr - -func (s *stack) Format(st fmt.State, verb rune) { - switch verb { - case 'v': - switch { - case st.Flag('+'): - for _, pc := range *s { - f := Frame(pc) - fmt.Fprintf(st, "\n%+v", f) - } - } - } -} - -func (s *stack) StackTrace() StackTrace { - f := make([]Frame, len(*s)) - for i := 0; i < len(f); i++ { - f[i] = Frame((*s)[i]) - } - return f -} - -func callers() *stack { - const depth = 32 - var pcs [depth]uintptr - n := runtime.Callers(3, pcs[:]) - var st stack = pcs[0:n] - return &st -} - -// funcname removes the path prefix component of a function's name reported by func.Name(). -func funcname(name string) string { - i := strings.LastIndex(name, "/") - name = name[i+1:] - i = strings.Index(name, ".") - return name[i+1:] -} - -func trimGOPATH(name, file string) string { - // Here we want to get the source file path relative to the compile time - // GOPATH. As of Go 1.6.x there is no direct way to know the compiled - // GOPATH at runtime, but we can infer the number of path segments in the - // GOPATH. We note that fn.Name() returns the function name qualified by - // the import path, which does not include the GOPATH. Thus we can trim - // segments from the beginning of the file path until the number of path - // separators remaining is one more than the number of path separators in - // the function name. For example, given: - // - // GOPATH /home/user - // file /home/user/src/pkg/sub/file.go - // fn.Name() pkg/sub.Type.Method - // - // We want to produce: - // - // pkg/sub/file.go - // - // From this we can easily see that fn.Name() has one less path separator - // than our desired output. We count separators from the end of the file - // path until it finds two more than in the function name and then move - // one character forward to preserve the initial path segment without a - // leading separator. - const sep = "/" - goal := strings.Count(name, sep) + 2 - i := len(file) - for n := 0; n < goal; n++ { - i = strings.LastIndex(file[:i], sep) - if i == -1 { - // not enough separators found, set i so that the slice expression - // below leaves file unmodified - i = -len(sep) - break - } - } - // get back to 0 or trim the leading separator - file = file[i+len(sep):] - return file -} diff --git a/vendor/github.com/slack-go/slack/.gitignore b/vendor/github.com/slack-go/slack/.gitignore index ac6f3ee..027f4de 100644 --- a/vendor/github.com/slack-go/slack/.gitignore +++ b/vendor/github.com/slack-go/slack/.gitignore @@ -1,3 +1,4 @@ *.test *~ .idea/ +/vendor/ \ No newline at end of file diff --git a/vendor/github.com/slack-go/slack/.golangci.yml b/vendor/github.com/slack-go/slack/.golangci.yml new file mode 100644 index 0000000..c16f538 --- /dev/null +++ b/vendor/github.com/slack-go/slack/.golangci.yml @@ -0,0 +1,14 @@ +run: + timeout: 6m + issues-exit-code: 1 +linters: + disable-all: true + enable: + - goimports + - govet + - interfacer + - misspell + - structcheck + - unconvert +issues: + new: true diff --git a/vendor/github.com/slack-go/slack/.gometalinter.json b/vendor/github.com/slack-go/slack/.gometalinter.json deleted file mode 100644 index 5fa629d..0000000 --- a/vendor/github.com/slack-go/slack/.gometalinter.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "DisableAll": true, - "Enable": [ - "structcheck", - "vet", - "misspell", - "unconvert", - "interfacer", - "goimports" - ], - "Vendor": true, - "Exclude": ["vendor"], - "Deadline": "300s" -} diff --git a/vendor/github.com/slack-go/slack/.travis.yml b/vendor/github.com/slack-go/slack/.travis.yml deleted file mode 100644 index 6a96823..0000000 --- a/vendor/github.com/slack-go/slack/.travis.yml +++ /dev/null @@ -1,39 +0,0 @@ -language: go - -env: - - GO111MODULE=on - -install: true - -before_install: - - export PATH=$HOME/gopath/bin:$PATH - # install gometalinter - - curl -L https://git.io/vp6lP | sh - -script: - - PATH=$PWD/bin:$PATH gometalinter ./... - - go test -race -cover ./... - -matrix: - allow_failures: - - go: tip - include: - - go: "1.7.x" - script: go test -v ./... - - go: "1.8.x" - script: go test -v ./... - - go: "1.9.x" - script: go test -v ./... - - go: "1.10.x" - script: go test -v ./... - - go: "1.11.x" - script: go test -v -mod=vendor ./... - - go: "1.12.x" - script: go test -v -mod=vendor ./... - - go: "1.13.x" - script: go test -v -mod=vendor ./... - - go: "tip" - script: go test -v -mod=vendor ./... - -git: - depth: 10 diff --git a/vendor/github.com/slack-go/slack/CHANGELOG.md b/vendor/github.com/slack-go/slack/CHANGELOG.md index 48bcce5..32da687 100644 --- a/vendor/github.com/slack-go/slack/CHANGELOG.md +++ b/vendor/github.com/slack-go/slack/CHANGELOG.md @@ -1,3 +1,47 @@ +### v0.7.0 - October 2, 2020 +full differences can be viewed using `git log --oneline --decorate --color v0.6.6..v0.7.0` +Thank you for many contributions! + +#### Breaking Changes +- Add ScheduledMessage type ([#753]) +- Add description field to option block object ([#783]) +- Fix wrong conditional branch ([#782]) + - The behavior of the user's application may change.(The current behavior is incorrect) + +#### Highlights +- example: fix to start up a server ([#773]) +- example: Add explanation how the message could be sent in a proper way ([#787]) +- example: fix typo in error log ([#779]) +- refactor: Make GetConversationsParameters.ExcludeArchived optional ([#791]) +- refactor: Unify variables to "config" ([#800]) +- refactor: Rename wrong file name ([#810]) +- feature: Add SetUserRealName for change user's realName([#755]) +- feature: Add response metadata to slack response ([#772]) +- feature: Add response metadata to slack response ([#778]) +- feature: Add select block element conversations filter field ([#790]) +- feature: Add Root field to MessageEvent to support thread_broadcast subtype ([#793]) +- feature: Add bot_profile to messages ([#794]) +- doc: Add logo to README ([#813]) +- doc: Update current project status and Add changelog for v0.7.0 ([#814]) + +[#753]: https://github.com/slack-go/slack/pull/753 +[#755]: https://github.com/slack-go/slack/pull/755 +[#772]: https://github.com/slack-go/slack/pull/772 +[#773]: https://github.com/slack-go/slack/pull/773 +[#778]: https://github.com/slack-go/slack/pull/778 +[#779]: https://github.com/slack-go/slack/pull/779 +[#782]: https://github.com/slack-go/slack/pull/782 +[#783]: https://github.com/slack-go/slack/pull/783 +[#787]: https://github.com/slack-go/slack/pull/787 +[#790]: https://github.com/slack-go/slack/pull/790 +[#791]: https://github.com/slack-go/slack/pull/791 +[#793]: https://github.com/slack-go/slack/pull/793 +[#794]: https://github.com/slack-go/slack/pull/794 +[#800]: https://github.com/slack-go/slack/pull/800 +[#810]: https://github.com/slack-go/slack/pull/810 +[#813]: https://github.com/slack-go/slack/pull/813 +[#814]: https://github.com/slack-go/slack/pull/814 + ### v0.6.0 - August 31, 2019 full differences can be viewed using `git log --oneline --decorate --color v0.5.0..v0.6.0` thanks to everyone who has contributed since January! diff --git a/vendor/github.com/slack-go/slack/README.md b/vendor/github.com/slack-go/slack/README.md index eaf0778..9618aeb 100644 --- a/vendor/github.com/slack-go/slack/README.md +++ b/vendor/github.com/slack-go/slack/README.md @@ -1,19 +1,21 @@ -Slack API in Go [![GoDoc](https://godoc.org/github.com/slack-go/slack?status.svg)](https://godoc.org/github.com/slack-go/slack) [![Build Status](https://travis-ci.org/slack-go/slack.svg)](https://travis-ci.org/slack-go/slack) +Slack API in Go [![Go Reference](https://pkg.go.dev/badge/github.com/slack-go/slack.svg)](https://pkg.go.dev/github.com/slack-go/slack) =============== -This is the original Slack library for Go created by Norberto Lopez, transferred to a Github organization. -[![Join the chat at https://gitter.im/go-slack/Lobby](https://badges.gitter.im/go-slack/Lobby.svg)](https://gitter.im/go-slack/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +This is the original Slack library for Go created by Norberto Lopes, transferred to a GitHub organization. + +You can also chat with us on the #slack-go, #slack-go-ja Slack channel on the Gophers Slack. + +![logo](logo.png "icon") This library supports most if not all of the `api.slack.com` REST calls, as well as the Real-Time Messaging protocol over websocket, in a fully managed way. +## Project Status +There is currently no major version released. +Therefore, minor version releases may include backward incompatible changes. - - -## Changelog - -[CHANGELOG.md](https://github.com/slack-go/slack/blob/master/CHANGELOG.md) is available. Please visit it for updates. +See [CHANGELOG.md](https://github.com/slack-go/slack/blob/master/CHANGELOG.md) or [Releases](https://github.com/slack-go/slack/releases) for more information about the changes. ## Installing @@ -37,7 +39,7 @@ func main() { // If you set debugging, it will log all requests to the console // Useful when encountering issues // slack.New("YOUR_TOKEN_HERE", slack.OptionDebug(true)) - groups, err := api.GetGroups(false) + groups, err := api.GetUserGroups(slack.GetUserGroupsOptionIncludeUsers(false)) if err != nil { fmt.Printf("%s\n", err) return @@ -68,8 +70,15 @@ func main() { } ``` +## Minimal Socket Mode usage: + +See https://github.com/slack-go/slack/blob/master/examples/socketmode/socketmode.go + + ## Minimal RTM usage: +As mentioned in https://api.slack.com/rtm - for most applications, Socket Mode is a better way to communicate with Slack. + See https://github.com/slack-go/slack/blob/master/examples/websocket/websocket.go @@ -77,7 +86,13 @@ See https://github.com/slack-go/slack/blob/master/examples/websocket/websocket.g See https://github.com/slack-go/slack/blob/master/examples/eventsapi/events.go +## Socketmode Event Handler (Experimental) + +When using socket mode, dealing with an event can be pretty lengthy as it requires you to route the event to the right place. + +Instead, you can use `SocketmodeHandler` much like you use an HTTP handler to register which event you would like to listen to and what callback function will process that event when it occurs. +See [./examples/socketmode_handler/socketmode_handler.go](./examples/socketmode_handler/socketmode_handler.go) ## Contributing You are more than welcome to contribute to this project. Fork and diff --git a/vendor/github.com/slack-go/slack/apps.go b/vendor/github.com/slack-go/slack/apps.go new file mode 100644 index 0000000..7322b15 --- /dev/null +++ b/vendor/github.com/slack-go/slack/apps.go @@ -0,0 +1,72 @@ +package slack + +import ( + "context" + "encoding/json" + "net/url" +) + +type listEventAuthorizationsResponse struct { + SlackResponse + Authorizations []EventAuthorization `json:"authorizations"` +} + +type EventAuthorization struct { + EnterpriseID string `json:"enterprise_id"` + TeamID string `json:"team_id"` + UserID string `json:"user_id"` + IsBot bool `json:"is_bot"` + IsEnterpriseInstall bool `json:"is_enterprise_install"` +} + +// ListEventAuthorizations lists authed users and teams for the given event_context. +// You must provide an app-level token to the client using OptionAppLevelToken. +// For more details, see ListEventAuthorizationsContext documentation. +func (api *Client) ListEventAuthorizations(eventContext string) ([]EventAuthorization, error) { + return api.ListEventAuthorizationsContext(context.Background(), eventContext) +} + +// ListEventAuthorizationsContext lists authed users and teams for the given event_context with a custom context. +// Slack API docs: https://api.slack.com/methods/apps.event.authorizations.list +func (api *Client) ListEventAuthorizationsContext(ctx context.Context, eventContext string) ([]EventAuthorization, error) { + resp := &listEventAuthorizationsResponse{} + + request, _ := json.Marshal(map[string]string{ + "event_context": eventContext, + }) + + err := postJSON(ctx, api.httpclient, api.endpoint+"apps.event.authorizations.list", api.appLevelToken, request, &resp, api) + + if err != nil { + return nil, err + } + if !resp.Ok { + return nil, resp.Err() + } + + return resp.Authorizations, nil +} + +// UninstallApp uninstalls your app from a workspace. +// For more details, see UninstallAppContext documentation. +func (api *Client) UninstallApp(clientID, clientSecret string) error { + return api.UninstallAppContext(context.Background(), clientID, clientSecret) +} + +// UninstallAppContext uninstalls your app from a workspace with a custom context. +// Slack API docs: https://api.slack.com/methods/apps.uninstall +func (api *Client) UninstallAppContext(ctx context.Context, clientID, clientSecret string) error { + values := url.Values{ + "client_id": {clientID}, + "client_secret": {clientSecret}, + } + + response := SlackResponse{} + + err := api.getMethod(ctx, "apps.uninstall", api.token, values, &response) + if err != nil { + return err + } + + return response.Err() +} diff --git a/vendor/github.com/slack-go/slack/attachments.go b/vendor/github.com/slack-go/slack/attachments.go index b5b79f9..f4eb9b9 100644 --- a/vendor/github.com/slack-go/slack/attachments.go +++ b/vendor/github.com/slack-go/slack/attachments.go @@ -17,7 +17,7 @@ type AttachmentAction struct { Name string `json:"name"` // Required. Text string `json:"text"` // Required. Style string `json:"style,omitempty"` // Optional. Allowed values: "default", "primary", "danger". - Type actionType `json:"type"` // Required. Must be set to "button" or "select". + Type ActionType `json:"type"` // Required. Must be set to "button" or "select". Value string `json:"value,omitempty"` // Optional. DataSource string `json:"data_source,omitempty"` // Optional. MinQueryLength int `json:"min_query_length,omitempty"` // Optional. Default value is 1. @@ -29,7 +29,7 @@ type AttachmentAction struct { } // actionType returns the type of the action -func (a AttachmentAction) actionType() actionType { +func (a AttachmentAction) actionType() ActionType { return a.Type } @@ -80,6 +80,11 @@ type Attachment struct { ImageURL string `json:"image_url,omitempty"` ThumbURL string `json:"thumb_url,omitempty"` + ServiceName string `json:"service_name,omitempty"` + ServiceIcon string `json:"service_icon,omitempty"` + FromURL string `json:"from_url,omitempty"` + OriginalURL string `json:"original_url,omitempty"` + Fields []AttachmentField `json:"fields,omitempty"` Actions []AttachmentAction `json:"actions,omitempty"` MarkdownIn []string `json:"mrkdwn_in,omitempty"` diff --git a/vendor/github.com/slack-go/slack/audit.go b/vendor/github.com/slack-go/slack/audit.go new file mode 100644 index 0000000..a3ea7eb --- /dev/null +++ b/vendor/github.com/slack-go/slack/audit.go @@ -0,0 +1,152 @@ +package slack + +import ( + "context" + "net/url" + "strconv" +) + +type AuditLogResponse struct { + Entries []AuditEntry `json:"entries"` + SlackResponse +} + +type AuditEntry struct { + ID string `json:"id"` + DateCreate int `json:"date_create"` + Action string `json:"action"` + Actor struct { + Type string `json:"type"` + User AuditUser `json:"user"` + } `json:"actor"` + Entity struct { + Type string `json:"type"` + // Only one of the below will be completed, based on the value of Type a user, a channel, a file, an app, a workspace, or an enterprise + User AuditUser `json:"user"` + Channel AuditChannel `json:"channel"` + File AuditFile `json:"file"` + App AuditApp `json:"app"` + Workspace AuditWorkspace `json:"workspace"` + Enterprise AuditEnterprise `json:"enterprise"` + } `json:"entity"` + Context struct { + Location struct { + Type string `json:"type"` + ID string `json:"id"` + Name string `json:"name"` + Domain string `json:"domain"` + } `json:"location"` + UA string `json:"ua"` + IPAddress string `json:"ip_address"` + } `json:"context"` + Details struct { + NewValue interface{} `json:"new_value"` + PreviousValue interface{} `json:"previous_value"` + MobileOnly bool `json:"mobile_only"` + WebOnly bool `json:"web_only"` + NonSSOOnly bool `json:"non_sso_only"` + ExportType string `json:"export_type"` + ExportStart string `json:"export_start_ts"` + ExportEnd string `json:"export_end_ts"` + } `json:"details"` +} + +type AuditUser struct { + ID string `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Team string `json:"team"` +} + +type AuditChannel struct { + ID string `json:"id"` + Name string `json:"name"` + Privacy string `json:"privacy"` + IsShared bool `json:"is_shared"` + IsOrgShared bool `json:"is_org_shared"` +} + +type AuditFile struct { + ID string `json:"id"` + Name string `json:"name"` + Filetype string `json:"filetype"` + Title string `json:"title"` +} + +type AuditApp struct { + ID string `json:"id"` + Name string `json:"name"` + IsDistributed bool `json:"is_distributed"` + IsDirectoryApproved bool `json:"is_directory_approved"` + IsWorkflowApp bool `json:"is_workflow_app"` + Scopes []string `json:"scopes"` +} + +type AuditWorkspace struct { + ID string `json:"id"` + Name string `json:"name"` + Domain string `json:"domain"` +} + +type AuditEnterprise struct { + ID string `json:"id"` + Name string `json:"name"` + Domain string `json:"domain"` +} + +// AuditLogParameters contains all the parameters necessary (including the optional ones) for a GetAuditLogs() request +type AuditLogParameters struct { + Limit int + Cursor string + Latest int + Oldest int + Action string + Actor string + Entity string +} + +func (api *Client) auditLogsRequest(ctx context.Context, path string, values url.Values) (*AuditLogResponse, error) { + response := &AuditLogResponse{} + err := api.getMethod(ctx, path, api.token, values, response) + if err != nil { + return nil, err + } + return response, response.Err() +} + +// GetAuditLogs retrieves a page of audit entires according to the parameters given +func (api *Client) GetAuditLogs(params AuditLogParameters) (entries []AuditEntry, nextCursor string, err error) { + return api.GetAuditLogsContext(context.Background(), params) +} + +// GetAuditLogsContext retrieves a page of audit entries according to the parameters given with a custom context +func (api *Client) GetAuditLogsContext(ctx context.Context, params AuditLogParameters) (entries []AuditEntry, nextCursor string, err error) { + values := url.Values{} + if params.Limit != 0 { + values.Add("limit", strconv.Itoa(params.Limit)) + } + if params.Oldest != 0 { + values.Add("oldest", strconv.Itoa(params.Oldest)) + } + if params.Latest != 0 { + values.Add("latest", strconv.Itoa(params.Latest)) + } + if params.Cursor != "" { + values.Add("cursor", params.Cursor) + } + if params.Action != "" { + values.Add("action", params.Action) + } + if params.Actor != "" { + values.Add("actor", params.Actor) + } + if params.Entity != "" { + values.Add("entity", params.Entity) + } + + response, err := api.auditLogsRequest(ctx, "audit/v1/logs", values) + if err != nil { + return nil, "", err + } + return response.Entries, response.ResponseMetadata.Cursor, response.Err() +} diff --git a/vendor/github.com/slack-go/slack/auth.go b/vendor/github.com/slack-go/slack/auth.go index f4f7f00..7fff1a1 100644 --- a/vendor/github.com/slack-go/slack/auth.go +++ b/vendor/github.com/slack-go/slack/auth.go @@ -22,12 +22,14 @@ func (api *Client) authRequest(ctx context.Context, path string, values url.Valu return response, response.Err() } -// SendAuthRevoke will send a revocation for our token +// SendAuthRevoke will send a revocation for our token. +// For more details, see SendAuthRevokeContext documentation. func (api *Client) SendAuthRevoke(token string) (*AuthRevokeResponse, error) { return api.SendAuthRevokeContext(context.Background(), token) } -// SendAuthRevokeContext will send a revocation request for our token to api.revoke with context +// SendAuthRevokeContext will send a revocation request for our token to api.revoke with a custom context. +// Slack API docs: https://api.slack.com/methods/auth.revoke func (api *Client) SendAuthRevokeContext(ctx context.Context, token string) (*AuthRevokeResponse, error) { if token == "" { token = api.token @@ -38,3 +40,38 @@ func (api *Client) SendAuthRevokeContext(ctx context.Context, token string) (*Au return api.authRequest(ctx, "auth.revoke", values) } + +type listTeamsResponse struct { + Teams []Team `json:"teams"` + SlackResponse +} + +type ListTeamsParameters struct { + Limit int + Cursor string +} + +// ListTeams returns all workspaces a token can access. +// For more details, see ListTeamsContext documentation. +func (api *Client) ListTeams(params ListTeamsParameters) ([]Team, string, error) { + return api.ListTeamsContext(context.Background(), params) +} + +// ListTeamsContext returns all workspaces a token can access with a custom context. +// Slack API docs: https://api.slack.com/methods/auth.teams.list +func (api *Client) ListTeamsContext(ctx context.Context, params ListTeamsParameters) ([]Team, string, error) { + values := url.Values{ + "token": {api.token}, + } + if params.Cursor != "" { + values.Add("cursor", params.Cursor) + } + + response := &listTeamsResponse{} + err := api.postMethod(ctx, "auth.teams.list", values, response) + if err != nil { + return nil, "", err + } + + return response.Teams, response.ResponseMetadata.Cursor, response.Err() +} diff --git a/vendor/github.com/slack-go/slack/block.go b/vendor/github.com/slack-go/slack/block.go index dbc3449..1d6ba2d 100644 --- a/vendor/github.com/slack-go/slack/block.go +++ b/vendor/github.com/slack-go/slack/block.go @@ -9,13 +9,16 @@ package slack type MessageBlockType string const ( - MBTSection MessageBlockType = "section" - MBTDivider MessageBlockType = "divider" - MBTImage MessageBlockType = "image" - MBTAction MessageBlockType = "actions" - MBTContext MessageBlockType = "context" - MBTFile MessageBlockType = "file" - MBTInput MessageBlockType = "input" + MBTSection MessageBlockType = "section" + MBTDivider MessageBlockType = "divider" + MBTImage MessageBlockType = "image" + MBTAction MessageBlockType = "actions" + MBTContext MessageBlockType = "context" + MBTFile MessageBlockType = "file" + MBTInput MessageBlockType = "input" + MBTHeader MessageBlockType = "header" + MBTRichText MessageBlockType = "rich_text" + MBTVideo MessageBlockType = "video" ) // Block defines an interface all block types should implement @@ -32,27 +35,34 @@ type Blocks struct { // BlockAction is the action callback sent when a block is interacted with type BlockAction struct { - ActionID string `json:"action_id"` - BlockID string `json:"block_id"` - Type actionType `json:"type"` - Text TextBlockObject `json:"text"` - Value string `json:"value"` - ActionTs string `json:"action_ts"` - SelectedOption OptionBlockObject `json:"selected_option"` - SelectedOptions []OptionBlockObject `json:"selected_options"` - SelectedUser string `json:"selected_user"` - SelectedChannel string `json:"selected_channel"` - SelectedConversation string `json:"selected_conversation"` - SelectedDate string `json:"selected_date"` - InitialOption OptionBlockObject `json:"initial_option"` - InitialUser string `json:"initial_user"` - InitialChannel string `json:"initial_channel"` - InitialConversation string `json:"initial_conversation"` - InitialDate string `json:"initial_date"` + ActionID string `json:"action_id"` + BlockID string `json:"block_id"` + Type ActionType `json:"type"` + Text TextBlockObject `json:"text"` + Value string `json:"value"` + Files []File `json:"files"` + ActionTs string `json:"action_ts"` + SelectedOption OptionBlockObject `json:"selected_option"` + SelectedOptions []OptionBlockObject `json:"selected_options"` + SelectedUser string `json:"selected_user"` + SelectedUsers []string `json:"selected_users"` + SelectedChannel string `json:"selected_channel"` + SelectedChannels []string `json:"selected_channels"` + SelectedConversation string `json:"selected_conversation"` + SelectedConversations []string `json:"selected_conversations"` + SelectedDate string `json:"selected_date"` + SelectedTime string `json:"selected_time"` + SelectedDateTime int64 `json:"selected_date_time"` + InitialOption OptionBlockObject `json:"initial_option"` + InitialUser string `json:"initial_user"` + InitialChannel string `json:"initial_channel"` + InitialConversation string `json:"initial_conversation"` + InitialDate string `json:"initial_date"` + InitialTime string `json:"initial_time"` } // actionType returns the type of the action -func (b BlockAction) actionType() actionType { +func (b BlockAction) actionType() ActionType { return b.Type } diff --git a/vendor/github.com/slack-go/slack/block_action.go b/vendor/github.com/slack-go/slack/block_action.go index fe46a95..c15e4a3 100644 --- a/vendor/github.com/slack-go/slack/block_action.go +++ b/vendor/github.com/slack-go/slack/block_action.go @@ -6,7 +6,7 @@ package slack type ActionBlock struct { Type MessageBlockType `json:"type"` BlockID string `json:"block_id,omitempty"` - Elements BlockElements `json:"elements"` + Elements *BlockElements `json:"elements"` } // BlockType returns the type of the block @@ -19,7 +19,7 @@ func NewActionBlock(blockID string, elements ...BlockElement) *ActionBlock { return &ActionBlock{ Type: MBTAction, BlockID: blockID, - Elements: BlockElements{ + Elements: &BlockElements{ ElementSet: elements, }, } diff --git a/vendor/github.com/slack-go/slack/block_context.go b/vendor/github.com/slack-go/slack/block_context.go index c37bf27..384fee2 100644 --- a/vendor/github.com/slack-go/slack/block_context.go +++ b/vendor/github.com/slack-go/slack/block_context.go @@ -3,7 +3,7 @@ package slack // ContextBlock defines data that is used to display message context, which can // include both images and text. // -// More Information: https://api.slack.com/reference/messaging/blocks#actions +// More Information: https://api.slack.com/reference/messaging/blocks#context type ContextBlock struct { Type MessageBlockType `json:"type"` BlockID string `json:"block_id,omitempty"` diff --git a/vendor/github.com/slack-go/slack/block_conv.go b/vendor/github.com/slack-go/slack/block_conv.go index 43c0c96..7570be2 100644 --- a/vendor/github.com/slack-go/slack/block_conv.go +++ b/vendor/github.com/slack-go/slack/block_conv.go @@ -2,8 +2,7 @@ package slack import ( "encoding/json" - - "github.com/pkg/errors" + "fmt" ) type sumtype struct { @@ -58,12 +57,20 @@ func (b *Blocks) UnmarshalJSON(data []byte) error { block = &DividerBlock{} case "file": block = &FileBlock{} + case "header": + block = &HeaderBlock{} case "image": block = &ImageBlock{} case "input": block = &InputBlock{} + case "rich_text": + block = &RichTextBlock{} + case "rich_text_input": + block = &RichTextBlock{} case "section": block = &SectionBlock{} + case "video": + block = &VideoBlock{} default: block = &UnknownBlock{} } @@ -104,12 +111,34 @@ func (b *InputBlock) UnmarshalJSON(data []byte) error { switch s.TypeVal { case "datepicker": e = &DatePickerBlockElement{} + case "timepicker": + e = &TimePickerBlockElement{} + case "datetimepicker": + e = &DateTimePickerBlockElement{} case "plain_text_input": e = &PlainTextInputBlockElement{} + case "rich_text_input": + e = &RichTextInputBlockElement{} + case "email_text_input": + e = &EmailTextInputBlockElement{} + case "url_text_input": + e = &URLTextInputBlockElement{} case "static_select", "external_select", "users_select", "conversations_select", "channels_select": e = &SelectBlockElement{} + case "multi_static_select", "multi_external_select", "multi_users_select", "multi_conversations_select", "multi_channels_select": + e = &MultiSelectBlockElement{} + case "checkboxes": + e = &CheckboxGroupsBlockElement{} + case "overflow": + e = &OverflowBlockElement{} + case "radio_buttons": + e = &RadioButtonsBlockElement{} + case "number_input": + e = &NumberInputBlockElement{} + case "file_input": + e = &FileInputBlockElement{} default: - return errors.New("unsupported block element type") + return fmt.Errorf("unsupported block element type %v", s.TypeVal) } if err := json.Unmarshal(a.Element, e); err != nil { @@ -168,12 +197,28 @@ func (b *BlockElements) UnmarshalJSON(data []byte) error { blockElement = &OverflowBlockElement{} case "datepicker": blockElement = &DatePickerBlockElement{} + case "timepicker": + blockElement = &TimePickerBlockElement{} + case "datetimepicker": + blockElement = &DateTimePickerBlockElement{} case "plain_text_input": blockElement = &PlainTextInputBlockElement{} + case "rich_text_input": + blockElement = &RichTextInputBlockElement{} + case "email_text_input": + blockElement = &EmailTextInputBlockElement{} + case "url_text_input": + blockElement = &URLTextInputBlockElement{} + case "checkboxes": + blockElement = &CheckboxGroupsBlockElement{} + case "radio_buttons": + blockElement = &RadioButtonsBlockElement{} case "static_select", "external_select", "users_select", "conversations_select", "channels_select": blockElement = &SelectBlockElement{} + case "number_input": + blockElement = &NumberInputBlockElement{} default: - return errors.New("unsupported block element type") + return fmt.Errorf("unsupported block element type %v", blockElementType) } err = json.Unmarshal(r, blockElement) @@ -201,6 +246,7 @@ func (a *Accessory) MarshalJSON() ([]byte, error) { // UnmarshalJSON implements the Unmarshaller interface for Accessory, so that any JSON // unmarshalling is delegated and proper type determination can be made before unmarshal +// Note: datetimepicker is not supported in Accessory func (a *Accessory) UnmarshalJSON(data []byte) error { var r json.RawMessage @@ -249,12 +295,24 @@ func (a *Accessory) UnmarshalJSON(data []byte) error { return err } a.DatePickerElement = element.(*DatePickerBlockElement) + case "timepicker": + element, err := unmarshalBlockElement(r, &TimePickerBlockElement{}) + if err != nil { + return err + } + a.TimePickerElement = element.(*TimePickerBlockElement) case "plain_text_input": element, err := unmarshalBlockElement(r, &PlainTextInputBlockElement{}) if err != nil { return err } a.PlainTextInputElement = element.(*PlainTextInputBlockElement) + case "rich_text_input": + element, err := unmarshalBlockElement(r, &RichTextInputBlockElement{}) + if err != nil { + return err + } + a.RichTextInputElement = element.(*RichTextInputBlockElement) case "radio_buttons": element, err := unmarshalBlockElement(r, &RadioButtonsBlockElement{}) if err != nil { @@ -273,6 +331,12 @@ func (a *Accessory) UnmarshalJSON(data []byte) error { return err } a.MultiSelectElement = element.(*MultiSelectBlockElement) + case "checkboxes": + element, err := unmarshalBlockElement(r, &CheckboxGroupsBlockElement{}) + if err != nil { + return err + } + a.CheckboxGroupsBlockElement = element.(*CheckboxGroupsBlockElement) default: element, err := unmarshalBlockElement(r, &UnknownBlockElement{}) if err != nil { @@ -305,12 +369,18 @@ func toBlockElement(element *Accessory) BlockElement { if element.DatePickerElement != nil { return element.DatePickerElement } + if element.TimePickerElement != nil { + return element.TimePickerElement + } if element.PlainTextInputElement != nil { return element.PlainTextInputElement } if element.RadioButtonsElement != nil { return element.RadioButtonsElement } + if element.CheckboxGroupsBlockElement != nil { + return element.CheckboxGroupsBlockElement + } if element.SelectElement != nil { return element.SelectElement } @@ -374,7 +444,7 @@ func (e *ContextElements) UnmarshalJSON(data []byte) error { e.Elements = append(e.Elements, elem.(*ImageBlockElement)) default: - return errors.New("unsupported context element type") + return fmt.Errorf("unsupported context element type %v", contextElementType) } } diff --git a/vendor/github.com/slack-go/slack/block_element.go b/vendor/github.com/slack-go/slack/block_element.go index 8460e95..a2b755b 100644 --- a/vendor/github.com/slack-go/slack/block_element.go +++ b/vendor/github.com/slack-go/slack/block_element.go @@ -3,12 +3,20 @@ package slack // https://api.slack.com/reference/messaging/block-elements const ( + METCheckboxGroups MessageElementType = "checkboxes" METImage MessageElementType = "image" METButton MessageElementType = "button" METOverflow MessageElementType = "overflow" METDatepicker MessageElementType = "datepicker" + METTimepicker MessageElementType = "timepicker" + METDatetimepicker MessageElementType = "datetimepicker" METPlainTextInput MessageElementType = "plain_text_input" METRadioButtons MessageElementType = "radio_buttons" + METRichTextInput MessageElementType = "rich_text_input" + METEmailTextInput MessageElementType = "email_text_input" + METURLTextInput MessageElementType = "url_text_input" + METNumber MessageElementType = "number_input" + METFileInput MessageElementType = "file_input" MixedElementImage MixedElementType = "mixed_image" MixedElementText MixedElementType = "mixed_text" @@ -39,15 +47,18 @@ type MixedElement interface { } type Accessory struct { - ImageElement *ImageBlockElement - ButtonElement *ButtonBlockElement - OverflowElement *OverflowBlockElement - DatePickerElement *DatePickerBlockElement - PlainTextInputElement *PlainTextInputBlockElement - RadioButtonsElement *RadioButtonsBlockElement - SelectElement *SelectBlockElement - MultiSelectElement *MultiSelectBlockElement - UnknownElement *UnknownBlockElement + ImageElement *ImageBlockElement + ButtonElement *ButtonBlockElement + OverflowElement *OverflowBlockElement + DatePickerElement *DatePickerBlockElement + TimePickerElement *TimePickerBlockElement + PlainTextInputElement *PlainTextInputBlockElement + RichTextInputElement *RichTextInputBlockElement + RadioButtonsElement *RadioButtonsBlockElement + SelectElement *SelectBlockElement + MultiSelectElement *MultiSelectBlockElement + CheckboxGroupsBlockElement *CheckboxGroupsBlockElement + UnknownElement *UnknownBlockElement } // NewAccessory returns a new Accessory for a given block element @@ -61,14 +72,20 @@ func NewAccessory(element BlockElement) *Accessory { return &Accessory{OverflowElement: element.(*OverflowBlockElement)} case *DatePickerBlockElement: return &Accessory{DatePickerElement: element.(*DatePickerBlockElement)} + case *TimePickerBlockElement: + return &Accessory{TimePickerElement: element.(*TimePickerBlockElement)} case *PlainTextInputBlockElement: return &Accessory{PlainTextInputElement: element.(*PlainTextInputBlockElement)} + case *RichTextInputBlockElement: + return &Accessory{RichTextInputElement: element.(*RichTextInputBlockElement)} case *RadioButtonsBlockElement: return &Accessory{RadioButtonsElement: element.(*RadioButtonsBlockElement)} case *SelectBlockElement: return &Accessory{SelectElement: element.(*SelectBlockElement)} case *MultiSelectBlockElement: return &Accessory{MultiSelectElement: element.(*MultiSelectBlockElement)} + case *CheckboxGroupsBlockElement: + return &Accessory{CheckboxGroupsBlockElement: element.(*CheckboxGroupsBlockElement)} default: return &Accessory{UnknownElement: element.(*UnknownBlockElement)} } @@ -123,10 +140,12 @@ func NewImageBlockElement(imageURL, altText string) *ImageBlockElement { } } +// Style is a style of Button element +// https://api.slack.com/reference/block-kit/block-elements#button__fields type Style string const ( - StyleDefault Style = "default" + StyleDefault Style = "" StylePrimary Style = "primary" StyleDanger Style = "danger" ) @@ -151,9 +170,22 @@ func (s ButtonBlockElement) ElementType() MessageElementType { return s.Type } -// add styling to button object -func (s *ButtonBlockElement) WithStyle(style Style) { +// WithStyle adds styling to the button object and returns the modified ButtonBlockElement +func (s *ButtonBlockElement) WithStyle(style Style) *ButtonBlockElement { s.Style = style + return s +} + +// WithConfirm adds a confirmation dialogue to the button object and returns the modified ButtonBlockElement +func (s *ButtonBlockElement) WithConfirm(confirm *ConfirmationBlockObject) *ButtonBlockElement { + s.Confirm = confirm + return s +} + +// WithURL adds a URL for the button to link to and returns the modified ButtonBlockElement +func (s *ButtonBlockElement) WithURL(url string) *ButtonBlockElement { + s.URL = url + return s } // NewButtonBlockElement returns an instance of a new button element to be used within a block @@ -185,17 +217,29 @@ type OptionGroupsResponse struct { // // More Information: https://api.slack.com/reference/messaging/block-elements#select type SelectBlockElement struct { - Type string `json:"type,omitempty"` - Placeholder *TextBlockObject `json:"placeholder,omitempty"` - ActionID string `json:"action_id,omitempty"` - Options []*OptionBlockObject `json:"options,omitempty"` - OptionGroups []*OptionGroupBlockObject `json:"option_groups,omitempty"` - InitialOption *OptionBlockObject `json:"initial_option,omitempty"` - InitialUser string `json:"initial_user,omitempty"` - InitialConversation string `json:"initial_conversation,omitempty"` - InitialChannel string `json:"initial_channel,omitempty"` - MinQueryLength *int `json:"min_query_length,omitempty"` - Confirm *ConfirmationBlockObject `json:"confirm,omitempty"` + Type string `json:"type,omitempty"` + Placeholder *TextBlockObject `json:"placeholder,omitempty"` + ActionID string `json:"action_id,omitempty"` + Options []*OptionBlockObject `json:"options,omitempty"` + OptionGroups []*OptionGroupBlockObject `json:"option_groups,omitempty"` + InitialOption *OptionBlockObject `json:"initial_option,omitempty"` + InitialUser string `json:"initial_user,omitempty"` + InitialConversation string `json:"initial_conversation,omitempty"` + InitialChannel string `json:"initial_channel,omitempty"` + DefaultToCurrentConversation bool `json:"default_to_current_conversation,omitempty"` + ResponseURLEnabled bool `json:"response_url_enabled,omitempty"` + Filter *SelectBlockElementFilter `json:"filter,omitempty"` + MinQueryLength *int `json:"min_query_length,omitempty"` + Confirm *ConfirmationBlockObject `json:"confirm,omitempty"` +} + +// SelectBlockElementFilter allows to filter select element conversation options by type. +// +// More Information: https://api.slack.com/reference/block-kit/composition-objects#filter_conversations +type SelectBlockElementFilter struct { + Include []string `json:"include,omitempty"` + ExcludeExternalSharedChannels bool `json:"exclude_external_shared_channels,omitempty"` + ExcludeBotUsers bool `json:"exclude_bot_users,omitempty"` } // ElementType returns the type of the Element @@ -245,6 +289,7 @@ type MultiSelectBlockElement struct { InitialConversations []string `json:"initial_conversations,omitempty"` InitialChannels []string `json:"initial_channels,omitempty"` MinQueryLength *int `json:"min_query_length,omitempty"` + MaxSelectedItems *int `json:"max_selected_items,omitempty"` Confirm *ConfirmationBlockObject `json:"confirm,omitempty"` } @@ -314,7 +359,7 @@ func NewOverflowBlockElement(actionID string, options ...*OptionBlockObject) *Ov // More Information: https://api.slack.com/reference/messaging/block-elements#datepicker type DatePickerBlockElement struct { Type MessageElementType `json:"type"` - ActionID string `json:"action_id"` + ActionID string `json:"action_id,omitempty"` Placeholder *TextBlockObject `json:"placeholder,omitempty"` InitialDate string `json:"initial_date,omitempty"` Confirm *ConfirmationBlockObject `json:"confirm,omitempty"` @@ -333,19 +378,131 @@ func NewDatePickerBlockElement(actionID string) *DatePickerBlockElement { } } +// TimePickerBlockElement defines an element which lets users easily select a +// time from nice UI. Time picker elements can be used inside of +// section and actions blocks. +// +// More Information: https://api.slack.com/reference/messaging/block-elements#timepicker +type TimePickerBlockElement struct { + Type MessageElementType `json:"type"` + ActionID string `json:"action_id,omitempty"` + Placeholder *TextBlockObject `json:"placeholder,omitempty"` + InitialTime string `json:"initial_time,omitempty"` + Confirm *ConfirmationBlockObject `json:"confirm,omitempty"` +} + +// ElementType returns the type of the Element +func (s TimePickerBlockElement) ElementType() MessageElementType { + return s.Type +} + +// NewTimePickerBlockElement returns an instance of a date picker element +func NewTimePickerBlockElement(actionID string) *TimePickerBlockElement { + return &TimePickerBlockElement{ + Type: METTimepicker, + ActionID: actionID, + } +} + +// DateTimePickerBlockElement defines an element that allows the selection of both +// a date and a time of day formatted as a UNIX timestamp. +// More Information: https://api.slack.com/reference/messaging/block-elements#datetimepicker +type DateTimePickerBlockElement struct { + Type MessageElementType `json:"type"` + ActionID string `json:"action_id,omitempty"` + InitialDateTime int64 `json:"initial_date_time,omitempty"` + Confirm *ConfirmationBlockObject `json:"confirm,omitempty"` +} + +// ElementType returns the type of the Element +func (s DateTimePickerBlockElement) ElementType() MessageElementType { + return s.Type +} + +// NewDatePickerBlockElement returns an instance of a datetime picker element +func NewDateTimePickerBlockElement(actionID string) *DateTimePickerBlockElement { + return &DateTimePickerBlockElement{ + Type: METDatetimepicker, + ActionID: actionID, + } +} + +// EmailTextInputBlockElement creates a field where a user can enter email +// data. +// email-text-input elements are currently only available in modals. +// +// More Information: https://api.slack.com/reference/block-kit/block-elements#email +type EmailTextInputBlockElement struct { + Type MessageElementType `json:"type"` + ActionID string `json:"action_id,omitempty"` + Placeholder *TextBlockObject `json:"placeholder,omitempty"` + InitialValue string `json:"initial_value,omitempty"` + DispatchActionConfig *DispatchActionConfig `json:"dispatch_action_config,omitempty"` + FocusOnLoad bool `json:"focus_on_load,omitempty"` +} + +// ElementType returns the type of the Element +func (s EmailTextInputBlockElement) ElementType() MessageElementType { + return s.Type +} + +// NewEmailTextInputBlockElement returns an instance of a plain-text input +// element +func NewEmailTextInputBlockElement(placeholder *TextBlockObject, actionID string) *EmailTextInputBlockElement { + return &EmailTextInputBlockElement{ + Type: METEmailTextInput, + ActionID: actionID, + Placeholder: placeholder, + } +} + +// URLTextInputBlockElement creates a field where a user can enter url data. +// +// url-text-input elements are currently only available in modals. +// +// More Information: https://api.slack.com/reference/block-kit/block-elements#url +type URLTextInputBlockElement struct { + Type MessageElementType `json:"type"` + ActionID string `json:"action_id,omitempty"` + Placeholder *TextBlockObject `json:"placeholder,omitempty"` + InitialValue string `json:"initial_value,omitempty"` + DispatchActionConfig *DispatchActionConfig `json:"dispatch_action_config,omitempty"` + FocusOnLoad bool `json:"focus_on_load,omitempty"` +} + +// ElementType returns the type of the Element +func (s URLTextInputBlockElement) ElementType() MessageElementType { + return s.Type +} + +// NewURLTextInputBlockElement returns an instance of a plain-text input +// element +func NewURLTextInputBlockElement(placeholder *TextBlockObject, actionID string) *URLTextInputBlockElement { + return &URLTextInputBlockElement{ + Type: METURLTextInput, + ActionID: actionID, + Placeholder: placeholder, + } +} + // PlainTextInputBlockElement creates a field where a user can enter freeform // data. // Plain-text input elements are currently only available in modals. // // More Information: https://api.slack.com/reference/block-kit/block-elements#input type PlainTextInputBlockElement struct { - Type MessageElementType `json:"type"` - ActionID string `json:"action_id"` - Placeholder *TextBlockObject `json:"placeholder,omitempty"` - InitialValue string `json:"initial_value,omitempty"` - Multiline bool `json:"multiline,omitempty"` - MinLength int `json:"min_length,omitempty"` - MaxLength int `json:"max_length,omitempty"` + Type MessageElementType `json:"type"` + ActionID string `json:"action_id,omitempty"` + Placeholder *TextBlockObject `json:"placeholder,omitempty"` + InitialValue string `json:"initial_value,omitempty"` + Multiline bool `json:"multiline,omitempty"` + MinLength int `json:"min_length,omitempty"` + MaxLength int `json:"max_length,omitempty"` + DispatchActionConfig *DispatchActionConfig `json:"dispatch_action_config,omitempty"` +} + +type DispatchActionConfig struct { + TriggerActionsOn []string `json:"trigger_actions_on,omitempty"` } // ElementType returns the type of the Element @@ -363,13 +520,65 @@ func NewPlainTextInputBlockElement(placeholder *TextBlockObject, actionID string } } +// RichTextInputBlockElement creates a field where allows users to enter formatted text +// in a WYSIWYG composer, offering the same messaging writing experience as in Slack +// More Information: https://api.slack.com/reference/block-kit/block-elements#rich_text_input +type RichTextInputBlockElement struct { + Type MessageElementType `json:"type"` + ActionID string `json:"action_id,omitempty"` + Placeholder *TextBlockObject `json:"placeholder,omitempty"` + InitialValue string `json:"initial_value,omitempty"` + DispatchActionConfig *DispatchActionConfig `json:"dispatch_action_config,omitempty"` + FocusOnLoad bool `json:"focus_on_load,omitempty"` +} + +// ElementType returns the type of the Element +func (s RichTextInputBlockElement) ElementType() MessageElementType { + return s.Type +} + +// NewRichTextInputBlockElement returns an instance of a rich-text input element +func NewRichTextInputBlockElement(placeholder *TextBlockObject, actionID string) *RichTextInputBlockElement { + return &RichTextInputBlockElement{ + Type: METRichTextInput, + ActionID: actionID, + Placeholder: placeholder, + } +} + +// CheckboxGroupsBlockElement defines an element which allows users to choose +// one or more items from a list of possible options. +// +// More Information: https://api.slack.com/reference/block-kit/block-elements#checkboxes +type CheckboxGroupsBlockElement struct { + Type MessageElementType `json:"type"` + ActionID string `json:"action_id,omitempty"` + Options []*OptionBlockObject `json:"options"` + InitialOptions []*OptionBlockObject `json:"initial_options,omitempty"` + Confirm *ConfirmationBlockObject `json:"confirm,omitempty"` +} + +// ElementType returns the type of the Element +func (c CheckboxGroupsBlockElement) ElementType() MessageElementType { + return c.Type +} + +// NewCheckboxGroupsBlockElement returns an instance of a checkbox-group block element +func NewCheckboxGroupsBlockElement(actionID string, options ...*OptionBlockObject) *CheckboxGroupsBlockElement { + return &CheckboxGroupsBlockElement{ + Type: METCheckboxGroups, + ActionID: actionID, + Options: options, + } +} + // RadioButtonsBlockElement defines an element which lets users choose one item // from a list of possible options. // // More Information: https://api.slack.com/reference/block-kit/block-elements#radio type RadioButtonsBlockElement struct { Type MessageElementType `json:"type"` - ActionID string `json:"action_id"` + ActionID string `json:"action_id,omitempty"` Options []*OptionBlockObject `json:"options"` InitialOption *OptionBlockObject `json:"initial_option,omitempty"` Confirm *ConfirmationBlockObject `json:"confirm,omitempty"` @@ -388,3 +597,71 @@ func NewRadioButtonsBlockElement(actionID string, options ...*OptionBlockObject) Options: options, } } + +// NumberInputBlockElement creates a field where a user can enter number +// data. +// Number input elements are currently only available in modals. +// +// More Information: https://api.slack.com/reference/block-kit/block-elements#number +type NumberInputBlockElement struct { + Type MessageElementType `json:"type"` + IsDecimalAllowed bool `json:"is_decimal_allowed"` + ActionID string `json:"action_id,omitempty"` + Placeholder *TextBlockObject `json:"placeholder,omitempty"` + InitialValue string `json:"initial_value,omitempty"` + MinValue string `json:"min_value,omitempty"` + MaxValue string `json:"max_value,omitempty"` + DispatchActionConfig *DispatchActionConfig `json:"dispatch_action_config,omitempty"` +} + +// ElementType returns the type of the Element +func (s NumberInputBlockElement) ElementType() MessageElementType { + return s.Type +} + +// NewNumberInputBlockElement returns an instance of a number input element +func NewNumberInputBlockElement(placeholder *TextBlockObject, actionID string, isDecimalAllowed bool) *NumberInputBlockElement { + return &NumberInputBlockElement{ + Type: METNumber, + ActionID: actionID, + Placeholder: placeholder, + IsDecimalAllowed: isDecimalAllowed, + } +} + +// FileInputBlockElement creates a field where a user can upload a file. +// +// File input elements are currently only available in modals. +// +// More Information: https://api.slack.com/reference/block-kit/block-elements#file_input +type FileInputBlockElement struct { + Type MessageElementType `json:"type"` + ActionID string `json:"action_id,omitempty"` + FileTypes []string `json:"filetypes,omitempty"` + MaxFiles int `json:"max_files,omitempty"` +} + +// ElementType returns the type of the Element +func (s FileInputBlockElement) ElementType() MessageElementType { + return s.Type +} + +// NewFileInputBlockElement returns an instance of a file input element +func NewFileInputBlockElement(actionID string) *FileInputBlockElement { + return &FileInputBlockElement{ + Type: METFileInput, + ActionID: actionID, + } +} + +// WithFileTypes sets the file types that can be uploaded +func (s *FileInputBlockElement) WithFileTypes(fileTypes ...string) *FileInputBlockElement { + s.FileTypes = fileTypes + return s +} + +// WithMaxFiles sets the maximum number of files that can be uploaded +func (s *FileInputBlockElement) WithMaxFiles(maxFiles int) *FileInputBlockElement { + s.MaxFiles = maxFiles + return s +} diff --git a/vendor/github.com/slack-go/slack/block_header.go b/vendor/github.com/slack-go/slack/block_header.go new file mode 100644 index 0000000..6dff4b8 --- /dev/null +++ b/vendor/github.com/slack-go/slack/block_header.go @@ -0,0 +1,38 @@ +package slack + +// HeaderBlock defines a new block of type header +// +// More Information: https://api.slack.com/reference/messaging/blocks#header +type HeaderBlock struct { + Type MessageBlockType `json:"type"` + Text *TextBlockObject `json:"text,omitempty"` + BlockID string `json:"block_id,omitempty"` +} + +// BlockType returns the type of the block +func (s HeaderBlock) BlockType() MessageBlockType { + return s.Type +} + +// HeaderBlockOption allows configuration of options for a new header block +type HeaderBlockOption func(*HeaderBlock) + +func HeaderBlockOptionBlockID(blockID string) HeaderBlockOption { + return func(block *HeaderBlock) { + block.BlockID = blockID + } +} + +// NewHeaderBlock returns a new instance of a header block to be rendered +func NewHeaderBlock(textObj *TextBlockObject, options ...HeaderBlockOption) *HeaderBlock { + block := HeaderBlock{ + Type: MBTHeader, + Text: textObj, + } + + for _, option := range options { + option(&block) + } + + return &block +} diff --git a/vendor/github.com/slack-go/slack/block_image.go b/vendor/github.com/slack-go/slack/block_image.go index 6de3f63..b3d2cb8 100644 --- a/vendor/github.com/slack-go/slack/block_image.go +++ b/vendor/github.com/slack-go/slack/block_image.go @@ -4,11 +4,21 @@ package slack // // More Information: https://api.slack.com/reference/messaging/blocks#image type ImageBlock struct { - Type MessageBlockType `json:"type"` - ImageURL string `json:"image_url"` - AltText string `json:"alt_text"` - BlockID string `json:"block_id,omitempty"` - Title *TextBlockObject `json:"title"` + Type MessageBlockType `json:"type"` + ImageURL string `json:"image_url,omitempty"` + AltText string `json:"alt_text"` + BlockID string `json:"block_id,omitempty"` + Title *TextBlockObject `json:"title,omitempty"` + SlackFile *SlackFileObject `json:"slack_file,omitempty"` +} + +// SlackFileObject Defines an object containing Slack file information to be used in an +// image block or image element. +// +// More Information: https://api.slack.com/reference/block-kit/composition-objects#slack_file +type SlackFileObject struct { + ID string `json:"id,omitempty"` + URL string `json:"url,omitempty"` } // BlockType returns the type of the block diff --git a/vendor/github.com/slack-go/slack/block_input.go b/vendor/github.com/slack-go/slack/block_input.go index 10638cd..78ffcdb 100644 --- a/vendor/github.com/slack-go/slack/block_input.go +++ b/vendor/github.com/slack-go/slack/block_input.go @@ -4,12 +4,13 @@ package slack // // More Information: https://api.slack.com/reference/block-kit/blocks#input type InputBlock struct { - Type MessageBlockType `json:"type"` - BlockID string `json:"block_id,omitempty"` - Label *TextBlockObject `json:"label"` - Element BlockElement `json:"element"` - Hint *TextBlockObject `json:"hint,omitempty"` - Optional bool `json:"optional,omitempty"` + Type MessageBlockType `json:"type"` + BlockID string `json:"block_id,omitempty"` + Label *TextBlockObject `json:"label"` + Element BlockElement `json:"element"` + Hint *TextBlockObject `json:"hint,omitempty"` + Optional bool `json:"optional,omitempty"` + DispatchAction bool `json:"dispatch_action,omitempty"` } // BlockType returns the type of the block @@ -18,11 +19,12 @@ func (s InputBlock) BlockType() MessageBlockType { } // NewInputBlock returns a new instance of an input block -func NewInputBlock(blockID string, label *TextBlockObject, element BlockElement) *InputBlock { +func NewInputBlock(blockID string, label, hint *TextBlockObject, element BlockElement) *InputBlock { return &InputBlock{ Type: MBTInput, BlockID: blockID, Label: label, Element: element, + Hint: hint, } } diff --git a/vendor/github.com/slack-go/slack/block_object.go b/vendor/github.com/slack-go/slack/block_object.go index cf3536d..a3e78d4 100644 --- a/vendor/github.com/slack-go/slack/block_object.go +++ b/vendor/github.com/slack-go/slack/block_object.go @@ -2,6 +2,7 @@ package slack import ( "encoding/json" + "errors" ) // Block Objects are also known as Composition Objects @@ -135,6 +136,30 @@ func (s TextBlockObject) MixedElementType() MixedElementType { return MixedElementText } +// Validate checks if TextBlockObject has valid values +func (s TextBlockObject) Validate() error { + if s.Type != "plain_text" && s.Type != "mrkdwn" { + return errors.New("type must be either of plain_text or mrkdwn") + } + + // https://github.com/slack-go/slack/issues/881 + if s.Type == "mrkdwn" && s.Emoji { + return errors.New("emoji cannot be true in mrkdown") + } + + // https://api.slack.com/reference/block-kit/composition-objects#text__fields + if len(s.Text) == 0 { + return errors.New("text must have a minimum length of 1") + } + + // https://api.slack.com/reference/block-kit/composition-objects#text__fields + if len(s.Text) > 3000 { + return errors.New("text cannot be longer than 3000 characters") + } + + return nil +} + // NewTextBlockObject returns an instance of a new Text Block Object func NewTextBlockObject(elementType, text string, emoji, verbatim bool) *TextBlockObject { return &TextBlockObject{ @@ -147,7 +172,7 @@ func NewTextBlockObject(elementType, text string, emoji, verbatim bool) *TextBlo // BlockType returns the type of the block func (t TextBlockObject) BlockType() MessageBlockType { - if t.Type == "mrkdown" { + if t.Type == "mrkdwn" { return MarkdownType } return PlainTextType @@ -162,7 +187,8 @@ type ConfirmationBlockObject struct { Title *TextBlockObject `json:"title"` Text *TextBlockObject `json:"text"` Confirm *TextBlockObject `json:"confirm"` - Deny *TextBlockObject `json:"deny"` + Deny *TextBlockObject `json:"deny,omitempty"` + Style Style `json:"style,omitempty"` } // validateType enforces block objects for element and block parameters @@ -170,6 +196,12 @@ func (s ConfirmationBlockObject) validateType() MessageObjectType { return motConfirmation } +// WithStyle add styling to confirmation object +func (s *ConfirmationBlockObject) WithStyle(style Style) *ConfirmationBlockObject { + s.Style = style + return s +} + // NewConfirmationBlockObject returns an instance of a new Confirmation Block Object func NewConfirmationBlockObject(title, text, confirm, deny *TextBlockObject) *ConfirmationBlockObject { return &ConfirmationBlockObject{ @@ -184,16 +216,18 @@ func NewConfirmationBlockObject(title, text, confirm, deny *TextBlockObject) *Co // // More Information: https://api.slack.com/reference/messaging/composition-objects#option type OptionBlockObject struct { - Text *TextBlockObject `json:"text"` - Value string `json:"value"` - URL string `json:"url,omitempty"` + Text *TextBlockObject `json:"text"` + Value string `json:"value"` + Description *TextBlockObject `json:"description,omitempty"` + URL string `json:"url,omitempty"` } // NewOptionBlockObject returns an instance of a new Option Block Element -func NewOptionBlockObject(value string, text *TextBlockObject) *OptionBlockObject { +func NewOptionBlockObject(value string, text, description *TextBlockObject) *OptionBlockObject { return &OptionBlockObject{ - Text: text, - Value: value, + Text: text, + Value: value, + Description: description, } } diff --git a/vendor/github.com/slack-go/slack/block_rich_text.go b/vendor/github.com/slack-go/slack/block_rich_text.go new file mode 100644 index 0000000..b6a4b4c --- /dev/null +++ b/vendor/github.com/slack-go/slack/block_rich_text.go @@ -0,0 +1,528 @@ +package slack + +import ( + "encoding/json" +) + +// RichTextBlock defines a new block of type rich_text. +// More Information: https://api.slack.com/changelog/2019-09-what-they-see-is-what-you-get-and-more-and-less +type RichTextBlock struct { + Type MessageBlockType `json:"type"` + BlockID string `json:"block_id,omitempty"` + Elements []RichTextElement `json:"elements"` +} + +func (b RichTextBlock) BlockType() MessageBlockType { + return b.Type +} + +func (e *RichTextBlock) UnmarshalJSON(b []byte) error { + var raw struct { + Type MessageBlockType `json:"type"` + BlockID string `json:"block_id"` + RawElements []json.RawMessage `json:"elements"` + } + if string(b) == "{}" { + return nil + } + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + elems := make([]RichTextElement, 0, len(raw.RawElements)) + for _, r := range raw.RawElements { + var s struct { + Type RichTextElementType `json:"type"` + } + if err := json.Unmarshal(r, &s); err != nil { + return err + } + var elem RichTextElement + switch s.Type { + case RTESection: + elem = &RichTextSection{} + case RTEList: + elem = &RichTextList{} + case RTEQuote: + elem = &RichTextQuote{} + case RTEPreformatted: + elem = &RichTextPreformatted{} + default: + elems = append(elems, &RichTextUnknown{ + Type: s.Type, + Raw: string(r), + }) + continue + } + if err := json.Unmarshal(r, &elem); err != nil { + return err + } + elems = append(elems, elem) + } + *e = RichTextBlock{ + Type: raw.Type, + BlockID: raw.BlockID, + Elements: elems, + } + return nil +} + +// NewRichTextBlock returns a new instance of RichText Block. +func NewRichTextBlock(blockID string, elements ...RichTextElement) *RichTextBlock { + return &RichTextBlock{ + Type: MBTRichText, + BlockID: blockID, + Elements: elements, + } +} + +type RichTextElementType string + +type RichTextElement interface { + RichTextElementType() RichTextElementType +} + +const ( + RTEList RichTextElementType = "rich_text_list" + RTEPreformatted RichTextElementType = "rich_text_preformatted" + RTEQuote RichTextElementType = "rich_text_quote" + RTESection RichTextElementType = "rich_text_section" + RTEUnknown RichTextElementType = "rich_text_unknown" +) + +type RichTextUnknown struct { + Type RichTextElementType + Raw string +} + +func (u RichTextUnknown) RichTextElementType() RichTextElementType { + return u.Type +} + +type RichTextListElementType string + +const ( + RTEListOrdered RichTextListElementType = "ordered" + RTEListBullet RichTextListElementType = "bullet" +) + +type RichTextList struct { + Type RichTextElementType `json:"type"` + Elements []RichTextElement `json:"elements"` + Style RichTextListElementType `json:"style"` + Indent int `json:"indent"` +} + +// NewRichTextList returns a new rich text list element. +func NewRichTextList(style RichTextListElementType, indent int, elements ...RichTextElement) *RichTextList { + return &RichTextList{ + Type: RTEList, + Elements: elements, + Style: style, + Indent: indent, + } +} + +// ElementType returns the type of the Element +func (s RichTextList) RichTextElementType() RichTextElementType { + return s.Type +} + +func (e *RichTextList) UnmarshalJSON(b []byte) error { + var raw struct { + RawElements []json.RawMessage `json:"elements"` + Style RichTextListElementType `json:"style"` + Indent int `json:"indent"` + } + if string(b) == "{}" { + return nil + } + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + elems := make([]RichTextElement, 0, len(raw.RawElements)) + for _, r := range raw.RawElements { + var s struct { + Type RichTextElementType `json:"type"` + } + if err := json.Unmarshal(r, &s); err != nil { + return err + } + var elem RichTextElement + switch s.Type { + case RTESection: + elem = &RichTextSection{} + case RTEList: + elem = &RichTextList{} + case RTEQuote: + elem = &RichTextQuote{} + case RTEPreformatted: + elem = &RichTextPreformatted{} + default: + elems = append(elems, &RichTextUnknown{ + Type: s.Type, + Raw: string(r), + }) + continue + } + if err := json.Unmarshal(r, elem); err != nil { + return err + } + elems = append(elems, elem) + } + *e = RichTextList{ + Type: RTEList, + Elements: elems, + Style: raw.Style, + Indent: raw.Indent, + } + return nil +} + +type RichTextSection struct { + Type RichTextElementType `json:"type"` + Elements []RichTextSectionElement `json:"elements"` +} + +// RichTextElementType returns the type of the Element +func (s RichTextSection) RichTextElementType() RichTextElementType { + return s.Type +} + +func (e *RichTextSection) UnmarshalJSON(b []byte) error { + var raw struct { + RawElements []json.RawMessage `json:"elements"` + } + if string(b) == "{}" { + return nil + } + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + elems := make([]RichTextSectionElement, 0, len(raw.RawElements)) + for _, r := range raw.RawElements { + var s struct { + Type RichTextSectionElementType `json:"type"` + } + if err := json.Unmarshal(r, &s); err != nil { + return err + } + var elem RichTextSectionElement + switch s.Type { + case RTSEText: + elem = &RichTextSectionTextElement{} + case RTSEChannel: + elem = &RichTextSectionChannelElement{} + case RTSEUser: + elem = &RichTextSectionUserElement{} + case RTSEEmoji: + elem = &RichTextSectionEmojiElement{} + case RTSELink: + elem = &RichTextSectionLinkElement{} + case RTSETeam: + elem = &RichTextSectionTeamElement{} + case RTSEUserGroup: + elem = &RichTextSectionUserGroupElement{} + case RTSEDate: + elem = &RichTextSectionDateElement{} + case RTSEBroadcast: + elem = &RichTextSectionBroadcastElement{} + case RTSEColor: + elem = &RichTextSectionColorElement{} + default: + elems = append(elems, &RichTextSectionUnknownElement{ + Type: s.Type, + Raw: string(r), + }) + continue + } + if err := json.Unmarshal(r, elem); err != nil { + return err + } + elems = append(elems, elem) + } + *e = RichTextSection{ + Type: RTESection, + Elements: elems, + } + return nil +} + +// NewRichTextSectionBlockElement . +func NewRichTextSection(elements ...RichTextSectionElement) *RichTextSection { + return &RichTextSection{ + Type: RTESection, + Elements: elements, + } +} + +type RichTextSectionElementType string + +const ( + RTSEBroadcast RichTextSectionElementType = "broadcast" + RTSEChannel RichTextSectionElementType = "channel" + RTSEColor RichTextSectionElementType = "color" + RTSEDate RichTextSectionElementType = "date" + RTSEEmoji RichTextSectionElementType = "emoji" + RTSELink RichTextSectionElementType = "link" + RTSETeam RichTextSectionElementType = "team" + RTSEText RichTextSectionElementType = "text" + RTSEUser RichTextSectionElementType = "user" + RTSEUserGroup RichTextSectionElementType = "usergroup" + + RTSEUnknown RichTextSectionElementType = "unknown" +) + +type RichTextSectionElement interface { + RichTextSectionElementType() RichTextSectionElementType +} + +type RichTextSectionTextStyle struct { + Bold bool `json:"bold,omitempty"` + Italic bool `json:"italic,omitempty"` + Strike bool `json:"strike,omitempty"` + Code bool `json:"code,omitempty"` +} + +type RichTextSectionTextElement struct { + Type RichTextSectionElementType `json:"type"` + Text string `json:"text"` + Style *RichTextSectionTextStyle `json:"style,omitempty"` +} + +func (r RichTextSectionTextElement) RichTextSectionElementType() RichTextSectionElementType { + return r.Type +} + +func NewRichTextSectionTextElement(text string, style *RichTextSectionTextStyle) *RichTextSectionTextElement { + return &RichTextSectionTextElement{ + Type: RTSEText, + Text: text, + Style: style, + } +} + +type RichTextSectionChannelElement struct { + Type RichTextSectionElementType `json:"type"` + ChannelID string `json:"channel_id"` + Style *RichTextSectionTextStyle `json:"style,omitempty"` +} + +func (r RichTextSectionChannelElement) RichTextSectionElementType() RichTextSectionElementType { + return r.Type +} + +func NewRichTextSectionChannelElement(channelID string, style *RichTextSectionTextStyle) *RichTextSectionChannelElement { + return &RichTextSectionChannelElement{ + Type: RTSEText, + ChannelID: channelID, + Style: style, + } +} + +type RichTextSectionUserElement struct { + Type RichTextSectionElementType `json:"type"` + UserID string `json:"user_id"` + Style *RichTextSectionTextStyle `json:"style,omitempty"` +} + +func (r RichTextSectionUserElement) RichTextSectionElementType() RichTextSectionElementType { + return r.Type +} + +func NewRichTextSectionUserElement(userID string, style *RichTextSectionTextStyle) *RichTextSectionUserElement { + return &RichTextSectionUserElement{ + Type: RTSEUser, + UserID: userID, + Style: style, + } +} + +type RichTextSectionEmojiElement struct { + Type RichTextSectionElementType `json:"type"` + Name string `json:"name"` + SkinTone int `json:"skin_tone"` + Style *RichTextSectionTextStyle `json:"style,omitempty"` +} + +func (r RichTextSectionEmojiElement) RichTextSectionElementType() RichTextSectionElementType { + return r.Type +} + +func NewRichTextSectionEmojiElement(name string, skinTone int, style *RichTextSectionTextStyle) *RichTextSectionEmojiElement { + return &RichTextSectionEmojiElement{ + Type: RTSEEmoji, + Name: name, + SkinTone: skinTone, + Style: style, + } +} + +type RichTextSectionLinkElement struct { + Type RichTextSectionElementType `json:"type"` + URL string `json:"url"` + Text string `json:"text"` + Style *RichTextSectionTextStyle `json:"style,omitempty"` +} + +func (r RichTextSectionLinkElement) RichTextSectionElementType() RichTextSectionElementType { + return r.Type +} + +func NewRichTextSectionLinkElement(url, text string, style *RichTextSectionTextStyle) *RichTextSectionLinkElement { + return &RichTextSectionLinkElement{ + Type: RTSELink, + URL: url, + Text: text, + Style: style, + } +} + +type RichTextSectionTeamElement struct { + Type RichTextSectionElementType `json:"type"` + TeamID string `json:"team_id"` + Style *RichTextSectionTextStyle `json:"style,omitempty"` +} + +func (r RichTextSectionTeamElement) RichTextSectionElementType() RichTextSectionElementType { + return r.Type +} + +func NewRichTextSectionTeamElement(teamID string, style *RichTextSectionTextStyle) *RichTextSectionTeamElement { + return &RichTextSectionTeamElement{ + Type: RTSETeam, + TeamID: teamID, + Style: style, + } +} + +type RichTextSectionUserGroupElement struct { + Type RichTextSectionElementType `json:"type"` + UsergroupID string `json:"usergroup_id"` +} + +func (r RichTextSectionUserGroupElement) RichTextSectionElementType() RichTextSectionElementType { + return r.Type +} + +func NewRichTextSectionUserGroupElement(usergroupID string) *RichTextSectionUserGroupElement { + return &RichTextSectionUserGroupElement{ + Type: RTSEUserGroup, + UsergroupID: usergroupID, + } +} + +type RichTextSectionDateElement struct { + Type RichTextSectionElementType `json:"type"` + Timestamp JSONTime `json:"timestamp"` +} + +func (r RichTextSectionDateElement) RichTextSectionElementType() RichTextSectionElementType { + return r.Type +} + +func NewRichTextSectionDateElement(timestamp int64) *RichTextSectionDateElement { + return &RichTextSectionDateElement{ + Type: RTSEDate, + Timestamp: JSONTime(timestamp), + } +} + +type RichTextSectionBroadcastElement struct { + Type RichTextSectionElementType `json:"type"` + Range string `json:"range"` +} + +func (r RichTextSectionBroadcastElement) RichTextSectionElementType() RichTextSectionElementType { + return r.Type +} + +func NewRichTextSectionBroadcastElement(rangeStr string) *RichTextSectionBroadcastElement { + return &RichTextSectionBroadcastElement{ + Type: RTSEBroadcast, + Range: rangeStr, + } +} + +type RichTextSectionColorElement struct { + Type RichTextSectionElementType `json:"type"` + Value string `json:"value"` +} + +func (r RichTextSectionColorElement) RichTextSectionElementType() RichTextSectionElementType { + return r.Type +} + +func NewRichTextSectionColorElement(value string) *RichTextSectionColorElement { + return &RichTextSectionColorElement{ + Type: RTSEColor, + Value: value, + } +} + +type RichTextSectionUnknownElement struct { + Type RichTextSectionElementType `json:"type"` + Raw string +} + +func (r RichTextSectionUnknownElement) RichTextSectionElementType() RichTextSectionElementType { + return r.Type +} + +// RichTextQuote represents rich_text_quote element type. +type RichTextQuote RichTextSection + +// RichTextElementType returns the type of the Element +func (s *RichTextQuote) RichTextElementType() RichTextElementType { + return s.Type +} + +func (s *RichTextQuote) UnmarshalJSON(b []byte) error { + // reusing the RichTextSection struct, as it's the same as RichTextQuote. + var rts RichTextSection + if err := json.Unmarshal(b, &rts); err != nil { + return err + } + *s = RichTextQuote(rts) + s.Type = RTEQuote + return nil +} + +// RichTextPreformatted represents rich_text_quote element type. +type RichTextPreformatted struct { + RichTextSection + Border int `json:"border"` +} + +// RichTextElementType returns the type of the Element +func (s *RichTextPreformatted) RichTextElementType() RichTextElementType { + return s.Type +} + +func (s *RichTextPreformatted) UnmarshalJSON(b []byte) error { + var rts RichTextSection + if err := json.Unmarshal(b, &rts); err != nil { + return err + } + // we define standalone fields because we need to unmarshal the border + // field. We can not directly unmarshal the data into + // RichTextPreformatted because it will cause an infinite loop. We also + // can not define a struct with embedded RichTextSection and Border fields + // because the json package will not unmarshal the data into the + // standalone fields, once it sees UnmarshalJSON method on the embedded + // struct. The drawback is that we have to process the data twice, and + // have to define a standalone struct with the same set of fields as the + // original struct, which may become a maintenance burden (i.e. update the + // fields in two places, should it ever change). + var standalone struct { + Border int `json:"border"` + } + if err := json.Unmarshal(b, &standalone); err != nil { + return err + } + *s = RichTextPreformatted{ + RichTextSection: rts, + Border: standalone.Border, + } + s.Type = RTEPreformatted + return nil +} diff --git a/vendor/github.com/slack-go/slack/block_video.go b/vendor/github.com/slack-go/slack/block_video.go new file mode 100644 index 0000000..322c614 --- /dev/null +++ b/vendor/github.com/slack-go/slack/block_video.go @@ -0,0 +1,65 @@ +package slack + +// VideoBlock defines data required to display a video as a block element +// +// More Information: https://api.slack.com/reference/block-kit/blocks#video +type VideoBlock struct { + Type MessageBlockType `json:"type"` + VideoURL string `json:"video_url"` + ThumbnailURL string `json:"thumbnail_url"` + AltText string `json:"alt_text"` + Title *TextBlockObject `json:"title"` + BlockID string `json:"block_id,omitempty"` + TitleURL string `json:"title_url,omitempty"` + AuthorName string `json:"author_name,omitempty"` + ProviderName string `json:"provider_name,omitempty"` + ProviderIconURL string `json:"provider_icon_url,omitempty"` + Description *TextBlockObject `json:"description,omitempty"` +} + +// BlockType returns the type of the block +func (s VideoBlock) BlockType() MessageBlockType { + return s.Type +} + +// NewVideoBlock returns an instance of a new Video Block type +func NewVideoBlock(videoURL, thumbnailURL, altText, blockID string, title *TextBlockObject) *VideoBlock { + return &VideoBlock{ + Type: MBTVideo, + VideoURL: videoURL, + ThumbnailURL: thumbnailURL, + AltText: altText, + BlockID: blockID, + Title: title, + } +} + +// WithAuthorName sets the author name for the VideoBlock +func (s *VideoBlock) WithAuthorName(authorName string) *VideoBlock { + s.AuthorName = authorName + return s +} + +// WithTitleURL sets the title URL for the VideoBlock +func (s *VideoBlock) WithTitleURL(titleURL string) *VideoBlock { + s.TitleURL = titleURL + return s +} + +// WithDescription sets the description for the VideoBlock +func (s *VideoBlock) WithDescription(description *TextBlockObject) *VideoBlock { + s.Description = description + return s +} + +// WithProviderIconURL sets the provider icon URL for the VideoBlock +func (s *VideoBlock) WithProviderIconURL(providerIconURL string) *VideoBlock { + s.ProviderIconURL = providerIconURL + return s +} + +// WithProviderName sets the provider name for the VideoBlock +func (s *VideoBlock) WithProviderName(providerName string) *VideoBlock { + s.ProviderName = providerName + return s +} diff --git a/vendor/github.com/slack-go/slack/bookmarks.go b/vendor/github.com/slack-go/slack/bookmarks.go new file mode 100644 index 0000000..1f07e59 --- /dev/null +++ b/vendor/github.com/slack-go/slack/bookmarks.go @@ -0,0 +1,169 @@ +package slack + +import ( + "context" + "net/url" +) + +type Bookmark struct { + ID string `json:"id"` + ChannelID string `json:"channel_id"` + Title string `json:"title"` + Link string `json:"link"` + Emoji string `json:"emoji"` + IconURL string `json:"icon_url"` + Type string `json:"type"` + Created JSONTime `json:"date_created"` + Updated JSONTime `json:"date_updated"` + Rank string `json:"rank"` + + LastUpdatedByUserID string `json:"last_updated_by_user_id"` + LastUpdatedByTeamID string `json:"last_updated_by_team_id"` + + ShortcutID string `json:"shortcut_id"` + EntityID string `json:"entity_id"` + AppID string `json:"app_id"` +} + +type AddBookmarkParameters struct { + Title string // A required title for the bookmark + Type string // A required type for the bookmark + Link string // URL required for type:link + Emoji string // An optional emoji + EntityID string + ParentID string +} + +type EditBookmarkParameters struct { + Title *string // Change the title. Set to "" to clear + Emoji *string // Change the emoji. Set to "" to clear + Link string // Change the link +} + +type addBookmarkResponse struct { + Bookmark Bookmark `json:"bookmark"` + SlackResponse +} + +type editBookmarkResponse struct { + Bookmark Bookmark `json:"bookmark"` + SlackResponse +} + +type listBookmarksResponse struct { + Bookmarks []Bookmark `json:"bookmarks"` + SlackResponse +} + +// AddBookmark adds a bookmark in a channel. +// For more details, see AddBookmarkContext documentation. +func (api *Client) AddBookmark(channelID string, params AddBookmarkParameters) (Bookmark, error) { + return api.AddBookmarkContext(context.Background(), channelID, params) +} + +// AddBookmarkContext adds a bookmark in a channel with a custom context. +// Slack API docs: https://api.slack.com/methods/bookmarks.add +func (api *Client) AddBookmarkContext(ctx context.Context, channelID string, params AddBookmarkParameters) (Bookmark, error) { + values := url.Values{ + "channel_id": {channelID}, + "token": {api.token}, + "title": {params.Title}, + "type": {params.Type}, + } + if params.Link != "" { + values.Set("link", params.Link) + } + if params.Emoji != "" { + values.Set("emoji", params.Emoji) + } + if params.EntityID != "" { + values.Set("entity_id", params.EntityID) + } + if params.ParentID != "" { + values.Set("parent_id", params.ParentID) + } + + response := &addBookmarkResponse{} + if err := api.postMethod(ctx, "bookmarks.add", values, response); err != nil { + return Bookmark{}, err + } + + return response.Bookmark, response.Err() +} + +// RemoveBookmark removes a bookmark from a channel. +// For more details, see RemoveBookmarkContext documentation. +func (api *Client) RemoveBookmark(channelID, bookmarkID string) error { + return api.RemoveBookmarkContext(context.Background(), channelID, bookmarkID) +} + +// RemoveBookmarkContext removes a bookmark from a channel with a custom context. +// Slack API docs: https://api.slack.com/methods/bookmarks.remove +func (api *Client) RemoveBookmarkContext(ctx context.Context, channelID, bookmarkID string) error { + values := url.Values{ + "channel_id": {channelID}, + "token": {api.token}, + "bookmark_id": {bookmarkID}, + } + + response := &SlackResponse{} + if err := api.postMethod(ctx, "bookmarks.remove", values, response); err != nil { + return err + } + + return response.Err() +} + +// ListBookmarks returns all bookmarks for a channel. +// For more details, see ListBookmarksContext documentation. +func (api *Client) ListBookmarks(channelID string) ([]Bookmark, error) { + return api.ListBookmarksContext(context.Background(), channelID) +} + +// ListBookmarksContext returns all bookmarks for a channel with a custom context. +// Slack API docs: https://api.slack.com/methods/bookmarks.edit +func (api *Client) ListBookmarksContext(ctx context.Context, channelID string) ([]Bookmark, error) { + values := url.Values{ + "channel_id": {channelID}, + "token": {api.token}, + } + + response := &listBookmarksResponse{} + err := api.postMethod(ctx, "bookmarks.list", values, response) + if err != nil { + return nil, err + } + return response.Bookmarks, response.Err() +} + +// EditBookmark edits a bookmark in a channel. +// For more details, see EditBookmarkContext documentation. +func (api *Client) EditBookmark(channelID, bookmarkID string, params EditBookmarkParameters) (Bookmark, error) { + return api.EditBookmarkContext(context.Background(), channelID, bookmarkID, params) +} + +// EditBookmarkContext edits a bookmark in a channel with a custom context. +// Slack API docs: https://api.slack.com/methods/bookmarks.edit +func (api *Client) EditBookmarkContext(ctx context.Context, channelID, bookmarkID string, params EditBookmarkParameters) (Bookmark, error) { + values := url.Values{ + "channel_id": {channelID}, + "token": {api.token}, + "bookmark_id": {bookmarkID}, + } + if params.Link != "" { + values.Set("link", params.Link) + } + if params.Emoji != nil { + values.Set("emoji", *params.Emoji) + } + if params.Title != nil { + values.Set("title", *params.Title) + } + + response := &editBookmarkResponse{} + if err := api.postMethod(ctx, "bookmarks.edit", values, response); err != nil { + return Bookmark{}, err + } + + return response.Bookmark, response.Err() +} diff --git a/vendor/github.com/slack-go/slack/bots.go b/vendor/github.com/slack-go/slack/bots.go index da21ba0..1ab9469 100644 --- a/vendor/github.com/slack-go/slack/bots.go +++ b/vendor/github.com/slack-go/slack/bots.go @@ -35,19 +35,30 @@ func (api *Client) botRequest(ctx context.Context, path string, values url.Value return response, nil } -// GetBotInfo will retrieve the complete bot information -func (api *Client) GetBotInfo(bot string) (*Bot, error) { - return api.GetBotInfoContext(context.Background(), bot) +type GetBotInfoParameters struct { + Bot string + TeamID string } -// GetBotInfoContext will retrieve the complete bot information using a custom context -func (api *Client) GetBotInfoContext(ctx context.Context, bot string) (*Bot, error) { +// GetBotInfo will retrieve the complete bot information. +// For more details, see GetBotInfoContext documentation. +func (api *Client) GetBotInfo(parameters GetBotInfoParameters) (*Bot, error) { + return api.GetBotInfoContext(context.Background(), parameters) +} + +// GetBotInfoContext will retrieve the complete bot information using a custom context. +// Slack API docs: https://api.slack.com/methods/bots.info +func (api *Client) GetBotInfoContext(ctx context.Context, parameters GetBotInfoParameters) (*Bot, error) { values := url.Values{ "token": {api.token}, } - if bot != "" { - values.Add("bot", bot) + if parameters.Bot != "" { + values.Add("bot", parameters.Bot) + } + + if parameters.TeamID != "" { + values.Add("team_id", parameters.TeamID) } response, err := api.botRequest(ctx, "bots.info", values) diff --git a/vendor/github.com/slack-go/slack/channels.go b/vendor/github.com/slack-go/slack/channels.go index a90d238..88d567b 100644 --- a/vendor/github.com/slack-go/slack/channels.go +++ b/vendor/github.com/slack-go/slack/channels.go @@ -3,8 +3,6 @@ package slack import ( "context" "net/url" - "strconv" - "time" ) type channelResponseFull struct { @@ -21,10 +19,11 @@ type channelResponseFull struct { // Channel contains information about the channel type Channel struct { GroupConversation - IsChannel bool `json:"is_channel"` - IsGeneral bool `json:"is_general"` - IsMember bool `json:"is_member"` - Locale string `json:"locale"` + IsChannel bool `json:"is_channel"` + IsGeneral bool `json:"is_general"` + IsMember bool `json:"is_member"` + Locale string `json:"locale"` + Properties *Properties `json:"properties"` } func (api *Client) channelRequest(ctx context.Context, path string, values url.Values) (*channelResponseFull, error) { @@ -36,446 +35,3 @@ func (api *Client) channelRequest(ctx context.Context, path string, values url.V return response, response.Err() } - -// GetChannelsOption option provided when getting channels. -type GetChannelsOption func(*ChannelPagination) error - -// GetChannelsOptionExcludeMembers excludes the members collection from each channel. -func GetChannelsOptionExcludeMembers() GetChannelsOption { - return func(p *ChannelPagination) error { - p.excludeMembers = true - return nil - } -} - -// GetChannelsOptionExcludeArchived excludes archived channels from results. -func GetChannelsOptionExcludeArchived() GetChannelsOption { - return func(p *ChannelPagination) error { - p.excludeArchived = true - return nil - } -} - -// ArchiveChannel archives the given channel -// see https://api.slack.com/methods/channels.archive -func (api *Client) ArchiveChannel(channelID string) error { - return api.ArchiveChannelContext(context.Background(), channelID) -} - -// ArchiveChannelContext archives the given channel with a custom context -// see https://api.slack.com/methods/channels.archive -func (api *Client) ArchiveChannelContext(ctx context.Context, channelID string) (err error) { - values := url.Values{ - "token": {api.token}, - "channel": {channelID}, - } - - _, err = api.channelRequest(ctx, "channels.archive", values) - return err -} - -// UnarchiveChannel unarchives the given channel -// see https://api.slack.com/methods/channels.unarchive -func (api *Client) UnarchiveChannel(channelID string) error { - return api.UnarchiveChannelContext(context.Background(), channelID) -} - -// UnarchiveChannelContext unarchives the given channel with a custom context -// see https://api.slack.com/methods/channels.unarchive -func (api *Client) UnarchiveChannelContext(ctx context.Context, channelID string) (err error) { - values := url.Values{ - "token": {api.token}, - "channel": {channelID}, - } - - _, err = api.channelRequest(ctx, "channels.unarchive", values) - return err -} - -// CreateChannel creates a channel with the given name and returns a *Channel -// see https://api.slack.com/methods/channels.create -func (api *Client) CreateChannel(channelName string) (*Channel, error) { - return api.CreateChannelContext(context.Background(), channelName) -} - -// CreateChannelContext creates a channel with the given name and returns a *Channel with a custom context -// see https://api.slack.com/methods/channels.create -func (api *Client) CreateChannelContext(ctx context.Context, channelName string) (*Channel, error) { - values := url.Values{ - "token": {api.token}, - "name": {channelName}, - } - - response, err := api.channelRequest(ctx, "channels.create", values) - if err != nil { - return nil, err - } - return &response.Channel, nil -} - -// GetChannelHistory retrieves the channel history -// see https://api.slack.com/methods/channels.history -func (api *Client) GetChannelHistory(channelID string, params HistoryParameters) (*History, error) { - return api.GetChannelHistoryContext(context.Background(), channelID, params) -} - -// GetChannelHistoryContext retrieves the channel history with a custom context -// see https://api.slack.com/methods/channels.history -func (api *Client) GetChannelHistoryContext(ctx context.Context, channelID string, params HistoryParameters) (*History, error) { - values := url.Values{ - "token": {api.token}, - "channel": {channelID}, - } - if params.Latest != DEFAULT_HISTORY_LATEST { - values.Add("latest", params.Latest) - } - if params.Oldest != DEFAULT_HISTORY_OLDEST { - values.Add("oldest", params.Oldest) - } - if params.Count != DEFAULT_HISTORY_COUNT { - values.Add("count", strconv.Itoa(params.Count)) - } - if params.Inclusive != DEFAULT_HISTORY_INCLUSIVE { - if params.Inclusive { - values.Add("inclusive", "1") - } else { - values.Add("inclusive", "0") - } - } - - if params.Unreads != DEFAULT_HISTORY_UNREADS { - if params.Unreads { - values.Add("unreads", "1") - } else { - values.Add("unreads", "0") - } - } - - response, err := api.channelRequest(ctx, "channels.history", values) - if err != nil { - return nil, err - } - return &response.History, nil -} - -// GetChannelInfo retrieves the given channel -// see https://api.slack.com/methods/channels.info -func (api *Client) GetChannelInfo(channelID string) (*Channel, error) { - return api.GetChannelInfoContext(context.Background(), channelID) -} - -// GetChannelInfoContext retrieves the given channel with a custom context -// see https://api.slack.com/methods/channels.info -func (api *Client) GetChannelInfoContext(ctx context.Context, channelID string) (*Channel, error) { - values := url.Values{ - "token": {api.token}, - "channel": {channelID}, - "include_locale": {strconv.FormatBool(true)}, - } - - response, err := api.channelRequest(ctx, "channels.info", values) - if err != nil { - return nil, err - } - return &response.Channel, nil -} - -// InviteUserToChannel invites a user to a given channel and returns a *Channel -// see https://api.slack.com/methods/channels.invite -func (api *Client) InviteUserToChannel(channelID, user string) (*Channel, error) { - return api.InviteUserToChannelContext(context.Background(), channelID, user) -} - -// InviteUserToChannelContext invites a user to a given channel and returns a *Channel with a custom context -// see https://api.slack.com/methods/channels.invite -func (api *Client) InviteUserToChannelContext(ctx context.Context, channelID, user string) (*Channel, error) { - values := url.Values{ - "token": {api.token}, - "channel": {channelID}, - "user": {user}, - } - - response, err := api.channelRequest(ctx, "channels.invite", values) - if err != nil { - return nil, err - } - return &response.Channel, nil -} - -// JoinChannel joins the currently authenticated user to a channel -// see https://api.slack.com/methods/channels.join -func (api *Client) JoinChannel(channelName string) (*Channel, error) { - return api.JoinChannelContext(context.Background(), channelName) -} - -// JoinChannelContext joins the currently authenticated user to a channel with a custom context -// see https://api.slack.com/methods/channels.join -func (api *Client) JoinChannelContext(ctx context.Context, channelName string) (*Channel, error) { - values := url.Values{ - "token": {api.token}, - "name": {channelName}, - } - - response, err := api.channelRequest(ctx, "channels.join", values) - if err != nil { - return nil, err - } - return &response.Channel, nil -} - -// LeaveChannel makes the authenticated user leave the given channel -// see https://api.slack.com/methods/channels.leave -func (api *Client) LeaveChannel(channelID string) (bool, error) { - return api.LeaveChannelContext(context.Background(), channelID) -} - -// LeaveChannelContext makes the authenticated user leave the given channel with a custom context -// see https://api.slack.com/methods/channels.leave -func (api *Client) LeaveChannelContext(ctx context.Context, channelID string) (bool, error) { - values := url.Values{ - "token": {api.token}, - "channel": {channelID}, - } - - response, err := api.channelRequest(ctx, "channels.leave", values) - if err != nil { - return false, err - } - - return response.NotInChannel, nil -} - -// KickUserFromChannel kicks a user from a given channel -// see https://api.slack.com/methods/channels.kick -func (api *Client) KickUserFromChannel(channelID, user string) error { - return api.KickUserFromChannelContext(context.Background(), channelID, user) -} - -// KickUserFromChannelContext kicks a user from a given channel with a custom context -// see https://api.slack.com/methods/channels.kick -func (api *Client) KickUserFromChannelContext(ctx context.Context, channelID, user string) (err error) { - values := url.Values{ - "token": {api.token}, - "channel": {channelID}, - "user": {user}, - } - - _, err = api.channelRequest(ctx, "channels.kick", values) - return err -} - -func newChannelPagination(c *Client, options ...GetChannelsOption) (cp ChannelPagination) { - cp = ChannelPagination{ - c: c, - limit: 200, // per slack api documentation. - } - - for _, opt := range options { - opt(&cp) - } - - return cp -} - -// ChannelPagination allows for paginating over the channels -type ChannelPagination struct { - Channels []Channel - limit int - excludeArchived bool - excludeMembers bool - previousResp *ResponseMetadata - c *Client -} - -// Done checks if the pagination has completed -func (ChannelPagination) Done(err error) bool { - return err == errPaginationComplete -} - -// Failure checks if pagination failed. -func (t ChannelPagination) Failure(err error) error { - if t.Done(err) { - return nil - } - - return err -} - -func (t ChannelPagination) Next(ctx context.Context) (_ ChannelPagination, err error) { - var ( - resp *channelResponseFull - ) - - if t.c == nil || (t.previousResp != nil && t.previousResp.Cursor == "") { - return t, errPaginationComplete - } - - t.previousResp = t.previousResp.initialize() - - values := url.Values{ - "limit": {strconv.Itoa(t.limit)}, - "exclude_archived": {strconv.FormatBool(t.excludeArchived)}, - "exclude_members": {strconv.FormatBool(t.excludeMembers)}, - "token": {t.c.token}, - "cursor": {t.previousResp.Cursor}, - } - - if resp, err = t.c.channelRequest(ctx, "channels.list", values); err != nil { - return t, err - } - - t.c.Debugf("GetChannelsContext: got %d channels; metadata %v", len(resp.Channels), resp.Metadata) - t.Channels = resp.Channels - t.previousResp = &resp.Metadata - - return t, nil -} - -// GetChannelsPaginated fetches channels in a paginated fashion, see GetChannelsContext for usage. -func (api *Client) GetChannelsPaginated(options ...GetChannelsOption) ChannelPagination { - return newChannelPagination(api, options...) -} - -// GetChannels retrieves all the channels -// see https://api.slack.com/methods/channels.list -func (api *Client) GetChannels(excludeArchived bool, options ...GetChannelsOption) ([]Channel, error) { - return api.GetChannelsContext(context.Background(), excludeArchived, options...) -} - -// GetChannelsContext retrieves all the channels with a custom context -// see https://api.slack.com/methods/channels.list -func (api *Client) GetChannelsContext(ctx context.Context, excludeArchived bool, options ...GetChannelsOption) (results []Channel, err error) { - if excludeArchived { - options = append(options, GetChannelsOptionExcludeArchived()) - } - - p := api.GetChannelsPaginated(options...) - for err == nil { - p, err = p.Next(ctx) - if err == nil { - results = append(results, p.Channels...) - } else if rateLimitedError, ok := err.(*RateLimitedError); ok { - select { - case <-ctx.Done(): - err = ctx.Err() - case <-time.After(rateLimitedError.RetryAfter): - err = nil - } - } - } - - return results, p.Failure(err) -} - -// SetChannelReadMark sets the read mark of a given channel to a specific point -// Clients should try to avoid making this call too often. When needing to mark a read position, a client should set a -// timer before making the call. In this way, any further updates needed during the timeout will not generate extra calls -// (just one per channel). This is useful for when reading scroll-back history, or following a busy live channel. A -// timeout of 5 seconds is a good starting point. Be sure to flush these calls on shutdown/logout. -// see https://api.slack.com/methods/channels.mark -func (api *Client) SetChannelReadMark(channelID, ts string) error { - return api.SetChannelReadMarkContext(context.Background(), channelID, ts) -} - -// SetChannelReadMarkContext sets the read mark of a given channel to a specific point with a custom context -// For more details see SetChannelReadMark documentation -// see https://api.slack.com/methods/channels.mark -func (api *Client) SetChannelReadMarkContext(ctx context.Context, channelID, ts string) (err error) { - values := url.Values{ - "token": {api.token}, - "channel": {channelID}, - "ts": {ts}, - } - - _, err = api.channelRequest(ctx, "channels.mark", values) - return err -} - -// RenameChannel renames a given channel -// see https://api.slack.com/methods/channels.rename -func (api *Client) RenameChannel(channelID, name string) (*Channel, error) { - return api.RenameChannelContext(context.Background(), channelID, name) -} - -// RenameChannelContext renames a given channel with a custom context -// see https://api.slack.com/methods/channels.rename -func (api *Client) RenameChannelContext(ctx context.Context, channelID, name string) (*Channel, error) { - values := url.Values{ - "token": {api.token}, - "channel": {channelID}, - "name": {name}, - } - - // XXX: the created entry in this call returns a string instead of a number - // so I may have to do some workaround to solve it. - response, err := api.channelRequest(ctx, "channels.rename", values) - if err != nil { - return nil, err - } - return &response.Channel, nil -} - -// SetChannelPurpose sets the channel purpose and returns the purpose that was successfully set -// see https://api.slack.com/methods/channels.setPurpose -func (api *Client) SetChannelPurpose(channelID, purpose string) (string, error) { - return api.SetChannelPurposeContext(context.Background(), channelID, purpose) -} - -// SetChannelPurposeContext sets the channel purpose and returns the purpose that was successfully set with a custom context -// see https://api.slack.com/methods/channels.setPurpose -func (api *Client) SetChannelPurposeContext(ctx context.Context, channelID, purpose string) (string, error) { - values := url.Values{ - "token": {api.token}, - "channel": {channelID}, - "purpose": {purpose}, - } - - response, err := api.channelRequest(ctx, "channels.setPurpose", values) - if err != nil { - return "", err - } - return response.Purpose, nil -} - -// SetChannelTopic sets the channel topic and returns the topic that was successfully set -// see https://api.slack.com/methods/channels.setTopic -func (api *Client) SetChannelTopic(channelID, topic string) (string, error) { - return api.SetChannelTopicContext(context.Background(), channelID, topic) -} - -// SetChannelTopicContext sets the channel topic and returns the topic that was successfully set with a custom context -// see https://api.slack.com/methods/channels.setTopic -func (api *Client) SetChannelTopicContext(ctx context.Context, channelID, topic string) (string, error) { - values := url.Values{ - "token": {api.token}, - "channel": {channelID}, - "topic": {topic}, - } - - response, err := api.channelRequest(ctx, "channels.setTopic", values) - if err != nil { - return "", err - } - return response.Topic, nil -} - -// GetChannelReplies gets an entire thread (a message plus all the messages in reply to it). -// see https://api.slack.com/methods/channels.replies -func (api *Client) GetChannelReplies(channelID, thread_ts string) ([]Message, error) { - return api.GetChannelRepliesContext(context.Background(), channelID, thread_ts) -} - -// GetChannelRepliesContext gets an entire thread (a message plus all the messages in reply to it) with a custom context -// see https://api.slack.com/methods/channels.replies -func (api *Client) GetChannelRepliesContext(ctx context.Context, channelID, thread_ts string) ([]Message, error) { - values := url.Values{ - "token": {api.token}, - "channel": {channelID}, - "thread_ts": {thread_ts}, - } - response, err := api.channelRequest(ctx, "channels.replies", values) - if err != nil { - return nil, err - } - return response.History.Messages, nil -} diff --git a/vendor/github.com/slack-go/slack/chat.go b/vendor/github.com/slack-go/slack/chat.go index 1281b15..18f8e93 100644 --- a/vendor/github.com/slack-go/slack/chat.go +++ b/vendor/github.com/slack-go/slack/chat.go @@ -1,10 +1,13 @@ package slack import ( + "bytes" "context" "encoding/json" + "io" "net/http" "net/url" + "regexp" "strconv" "github.com/slack-go/slack/slackutilsx" @@ -27,14 +30,14 @@ const ( type chatResponseFull struct { Channel string `json:"channel"` - Timestamp string `json:"ts"` //Regular message timestamp - MessageTimeStamp string `json:"message_ts"` //Ephemeral message timestamp - ScheduledMessageID string `json:"scheduled_message_id,omitempty"` //Scheduled message id + Timestamp string `json:"ts"` // Regular message timestamp + MessageTimeStamp string `json:"message_ts"` // Ephemeral message timestamp + ScheduledMessageID string `json:"scheduled_message_id,omitempty"` // Scheduled message id Text string `json:"text"` SlackResponse } -// getMessageTimestamp will inspect the `chatResponseFull` to ruturn a timestamp value +// getMessageTimestamp will inspect the `chatResponseFull` to return a timestamp value // in `chat.postMessage` its under `ts` // in `chat.postEphemeral` its under `message_ts` func (c chatResponseFull) getMessageTimestamp() string { @@ -62,6 +65,9 @@ type PostMessageParameters struct { // chat.postEphemeral support Channel string `json:"channel"` User string `json:"user"` + + // chat metadata support + MetaData SlackMetadata `json:"metadata"` } // NewPostMessageParameters provides an instance of PostMessageParameters with all the sane default values set @@ -82,17 +88,14 @@ func NewPostMessageParameters() PostMessageParameters { } } -// DeleteMessage deletes a message in a channel +// DeleteMessage deletes a message in a channel. +// For more details, see DeleteMessageContext documentation. func (api *Client) DeleteMessage(channel, messageTimestamp string) (string, string, error) { - respChannel, respTimestamp, _, err := api.SendMessageContext( - context.Background(), - channel, - MsgOptionDelete(messageTimestamp), - ) - return respChannel, respTimestamp, err + return api.DeleteMessageContext(context.Background(), channel, messageTimestamp) } -// DeleteMessageContext deletes a message in a channel with a custom context +// DeleteMessageContext deletes a message in a channel with a custom context. +// Slack API docs: https://api.slack.com/methods/chat.delete func (api *Client) DeleteMessageContext(ctx context.Context, channel, messageTimestamp string) (string, string, error) { respChannel, respTimestamp, _, err := api.SendMessageContext( ctx, @@ -105,9 +108,16 @@ func (api *Client) DeleteMessageContext(ctx context.Context, channel, messageTim // ScheduleMessage sends a message to a channel. // Message is escaped by default according to https://api.slack.com/docs/formatting // Use http://davestevens.github.io/slack-message-builder/ to help crafting your message. +// For more details, see ScheduleMessageContext documentation. func (api *Client) ScheduleMessage(channelID, postAt string, options ...MsgOption) (string, string, error) { + return api.ScheduleMessageContext(context.Background(), channelID, postAt, options...) +} + +// ScheduleMessageContext sends a message to a channel with a custom context. +// Slack API docs: https://api.slack.com/methods/chat.scheduleMessage +func (api *Client) ScheduleMessageContext(ctx context.Context, channelID, postAt string, options ...MsgOption) (string, string, error) { respChannel, respTimestamp, _, err := api.SendMessageContext( - context.Background(), + ctx, channelID, MsgOptionSchedule(postAt), MsgOptionCompose(options...), @@ -118,18 +128,13 @@ func (api *Client) ScheduleMessage(channelID, postAt string, options ...MsgOptio // PostMessage sends a message to a channel. // Message is escaped by default according to https://api.slack.com/docs/formatting // Use http://davestevens.github.io/slack-message-builder/ to help crafting your message. +// For more details, see PostMessageContext documentation. func (api *Client) PostMessage(channelID string, options ...MsgOption) (string, string, error) { - respChannel, respTimestamp, _, err := api.SendMessageContext( - context.Background(), - channelID, - MsgOptionPost(), - MsgOptionCompose(options...), - ) - return respChannel, respTimestamp, err + return api.PostMessageContext(context.Background(), channelID, options...) } -// PostMessageContext sends a message to a channel with a custom context -// For more details, see PostMessage documentation. +// PostMessageContext sends a message to a channel with a custom context. +// Slack API docs: https://api.slack.com/methods/chat.postMessage func (api *Client) PostMessageContext(ctx context.Context, channelID string, options ...MsgOption) (string, string, error) { respChannel, respTimestamp, _, err := api.SendMessageContext( ctx, @@ -143,17 +148,13 @@ func (api *Client) PostMessageContext(ctx context.Context, channelID string, opt // PostEphemeral sends an ephemeral message to a user in a channel. // Message is escaped by default according to https://api.slack.com/docs/formatting // Use http://davestevens.github.io/slack-message-builder/ to help crafting your message. +// For more details, see PostEphemeralContext documentation. func (api *Client) PostEphemeral(channelID, userID string, options ...MsgOption) (string, error) { - return api.PostEphemeralContext( - context.Background(), - channelID, - userID, - options..., - ) + return api.PostEphemeralContext(context.Background(), channelID, userID, options...) } -// PostEphemeralContext sends an ephemeal message to a user in a channel with a custom context -// For more details, see PostEphemeral documentation +// PostEphemeralContext sends an ephemeral message to a user in a channel with a custom context. +// Slack API docs: https://api.slack.com/methods/chat.postEphemeral func (api *Client) PostEphemeralContext(ctx context.Context, channelID, userID string, options ...MsgOption) (timestamp string, err error) { _, timestamp, _, err = api.SendMessageContext( ctx, @@ -164,17 +165,14 @@ func (api *Client) PostEphemeralContext(ctx context.Context, channelID, userID s return timestamp, err } -// UpdateMessage updates a message in a channel +// UpdateMessage updates a message in a channel. +// For more details, see UpdateMessageContext documentation. func (api *Client) UpdateMessage(channelID, timestamp string, options ...MsgOption) (string, string, string, error) { - return api.SendMessageContext( - context.Background(), - channelID, - MsgOptionUpdate(timestamp), - MsgOptionCompose(options...), - ) + return api.UpdateMessageContext(context.Background(), channelID, timestamp, options...) } -// UpdateMessageContext updates a message in a channel +// UpdateMessageContext updates a message in a channel with a custom context. +// Slack API docs: https://api.slack.com/methods/chat.update func (api *Client) UpdateMessageContext(ctx context.Context, channelID, timestamp string, options ...MsgOption) (string, string, string, error) { return api.SendMessageContext( ctx, @@ -184,17 +182,38 @@ func (api *Client) UpdateMessageContext(ctx context.Context, channelID, timestam ) } -// UnfurlMessage unfurls a message in a channel +// UnfurlMessage unfurls a message in a channel. +// For more details, see UnfurlMessageContext documentation. func (api *Client) UnfurlMessage(channelID, timestamp string, unfurls map[string]Attachment, options ...MsgOption) (string, string, string, error) { - return api.SendMessageContext(context.Background(), channelID, MsgOptionUnfurl(timestamp, unfurls), MsgOptionCompose(options...)) + return api.UnfurlMessageContext(context.Background(), channelID, timestamp, unfurls, options...) +} + +// UnfurlMessageContext unfurls a message in a channel with a custom context. +// Slack API docs: https://api.slack.com/methods/chat.unfurl +func (api *Client) UnfurlMessageContext(ctx context.Context, channelID, timestamp string, unfurls map[string]Attachment, options ...MsgOption) (string, string, string, error) { + return api.SendMessageContext(ctx, channelID, MsgOptionUnfurl(timestamp, unfurls), MsgOptionCompose(options...)) +} + +// UnfurlMessageWithAuthURL sends an unfurl request containing an authentication URL. +// For more details, see UnfurlMessageWithAuthURLContext documentation. +func (api *Client) UnfurlMessageWithAuthURL(channelID, timestamp string, userAuthURL string, options ...MsgOption) (string, string, string, error) { + return api.UnfurlMessageWithAuthURLContext(context.Background(), channelID, timestamp, userAuthURL, options...) +} + +// UnfurlMessageWithAuthURLContext sends an unfurl request containing an authentication URL with a custom context. +// For more details see: https://api.slack.com/reference/messaging/link-unfurling#authenticated_unfurls +func (api *Client) UnfurlMessageWithAuthURLContext(ctx context.Context, channelID, timestamp string, userAuthURL string, options ...MsgOption) (string, string, string, error) { + return api.SendMessageContext(ctx, channelID, MsgOptionUnfurlAuthURL(timestamp, userAuthURL), MsgOptionCompose(options...)) } // SendMessage more flexible method for configuring messages. +// For more details, see SendMessageContext documentation. func (api *Client) SendMessage(channel string, options ...MsgOption) (string, string, string, error) { return api.SendMessageContext(context.Background(), channel, options...) } // SendMessageContext more flexible method for configuring messages with a custom context. +// Slack API docs: https://api.slack.com/methods/chat.postMessage func (api *Client) SendMessageContext(ctx context.Context, channelID string, options ...MsgOption) (_channel string, _timestamp string, _text string, err error) { var ( req *http.Request @@ -202,10 +221,19 @@ func (api *Client) SendMessageContext(ctx context.Context, channelID string, opt response chatResponseFull ) - if req, parser, err = buildSender(api.endpoint, options...).BuildRequest(api.token, channelID); err != nil { + if req, parser, err = buildSender(api.endpoint, options...).BuildRequestContext(ctx, api.token, channelID); err != nil { return "", "", "", err } + if api.Debug() { + reqBody, err := io.ReadAll(req.Body) + if err != nil { + return "", "", "", err + } + req.Body = io.NopCloser(bytes.NewBuffer(reqBody)) + api.Debugf("Sending request: %s", redactToken(reqBody)) + } + if err = doPost(ctx, api.httpclient, req, parser(&response), api); err != nil { return "", "", "", err } @@ -213,6 +241,20 @@ func (api *Client) SendMessageContext(ctx context.Context, channelID string, opt return response.Channel, response.getMessageTimestamp(), response.Text, response.Err() } +func redactToken(b []byte) []byte { + // See https://api.slack.com/authentication/token-types + // and https://api.slack.com/authentication/rotation + re, err := regexp.Compile(`(token=x[a-z.]+)-[0-9A-Za-z-]+`) + if err != nil { + // The regular expression above should never result in errors, + // but just in case, do no harm. + return b + } + // Keep "token=" and the first element of the token, which identifies its type + // (this could be useful for debugging, e.g. when using a wrong token). + return re.ReplaceAll(b, []byte("$1-REDACTED")) +} + // UnsafeApplyMsgOptions utility function for debugging/testing chat requests. // NOTE: USE AT YOUR OWN RISK: No issues relating to the use of this function // will be supported by the library. @@ -261,17 +303,24 @@ const ( ) type sendConfig struct { - apiurl string - options []MsgOption - mode sendMode - endpoint string - values url.Values - attachments []Attachment - blocks Blocks - responseType string + apiurl string + options []MsgOption + mode sendMode + endpoint string + values url.Values + attachments []Attachment + metadata SlackMetadata + blocks Blocks + responseType string + replaceOriginal bool + deleteOriginal bool } func (t sendConfig) BuildRequest(token, channelID string) (req *http.Request, _ func(*chatResponseFull) responseParser, err error) { + return t.BuildRequestContext(context.Background(), token, channelID) +} + +func (t sendConfig) BuildRequestContext(ctx context.Context, token, channelID string) (req *http.Request, _ func(*chatResponseFull) responseParser, err error) { if t, err = applyMsgOptions(token, channelID, t.apiurl, t.options...); err != nil { return nil, nil, err } @@ -279,14 +328,17 @@ func (t sendConfig) BuildRequest(token, channelID string) (req *http.Request, _ switch t.mode { case chatResponse: return responseURLSender{ - endpoint: t.endpoint, - values: t.values, - attachments: t.attachments, - blocks: t.blocks, - responseType: t.responseType, - }.BuildRequest() + endpoint: t.endpoint, + values: t.values, + attachments: t.attachments, + metadata: t.metadata, + blocks: t.blocks, + responseType: t.responseType, + replaceOriginal: t.replaceOriginal, + deleteOriginal: t.deleteOriginal, + }.BuildRequestContext(ctx) default: - return formSender{endpoint: t.endpoint, values: t.values}.BuildRequest() + return formSender{endpoint: t.endpoint, values: t.values}.BuildRequestContext(ctx) } } @@ -296,27 +348,41 @@ type formSender struct { } func (t formSender) BuildRequest() (*http.Request, func(*chatResponseFull) responseParser, error) { - req, err := formReq(t.endpoint, t.values) + return t.BuildRequestContext(context.Background()) +} + +func (t formSender) BuildRequestContext(ctx context.Context) (*http.Request, func(*chatResponseFull) responseParser, error) { + req, err := formReq(ctx, t.endpoint, t.values) return req, func(resp *chatResponseFull) responseParser { return newJSONParser(resp) }, err } type responseURLSender struct { - endpoint string - values url.Values - attachments []Attachment - blocks Blocks - responseType string + endpoint string + values url.Values + attachments []Attachment + metadata SlackMetadata + blocks Blocks + responseType string + replaceOriginal bool + deleteOriginal bool } func (t responseURLSender) BuildRequest() (*http.Request, func(*chatResponseFull) responseParser, error) { - req, err := jsonReq(t.endpoint, Msg{ - Text: t.values.Get("text"), - Timestamp: t.values.Get("ts"), - Attachments: t.attachments, - Blocks: t.blocks, - ResponseType: t.responseType, + return t.BuildRequestContext(context.Background()) +} + +func (t responseURLSender) BuildRequestContext(ctx context.Context) (*http.Request, func(*chatResponseFull) responseParser, error) { + req, err := jsonReq(ctx, t.endpoint, Msg{ + Text: t.values.Get("text"), + Timestamp: t.values.Get("ts"), + Attachments: t.attachments, + Blocks: t.blocks, + Metadata: t.metadata, + ResponseType: t.responseType, + ReplaceOriginal: t.replaceOriginal, + DeleteOriginal: t.deleteOriginal, }) return req, func(resp *chatResponseFull) responseParser { return newContentTypeParser(resp) @@ -394,6 +460,38 @@ func MsgOptionUnfurl(timestamp string, unfurls map[string]Attachment) MsgOption } } +// MsgOptionUnfurlAuthURL unfurls a message using an auth url based on the timestamp. +func MsgOptionUnfurlAuthURL(timestamp string, userAuthURL string) MsgOption { + return func(config *sendConfig) error { + config.endpoint = config.apiurl + string(chatUnfurl) + config.values.Add("ts", timestamp) + config.values.Add("user_auth_url", userAuthURL) + return nil + } +} + +// MsgOptionUnfurlAuthRequired requests that the user installs the +// Slack app for unfurling. +func MsgOptionUnfurlAuthRequired(timestamp string) MsgOption { + return func(config *sendConfig) error { + config.endpoint = config.apiurl + string(chatUnfurl) + config.values.Add("ts", timestamp) + config.values.Add("user_auth_required", "true") + return nil + } +} + +// MsgOptionUnfurlAuthMessage attaches a message inviting the user to +// authenticate. +func MsgOptionUnfurlAuthMessage(timestamp string, msg string) MsgOption { + return func(config *sendConfig) error { + config.endpoint = config.apiurl + string(chatUnfurl) + config.values.Add("ts", timestamp) + config.values.Add("user_auth_message", msg) + return nil + } +} + // MsgOptionResponseURL supplies a url to use as the endpoint. func MsgOptionResponseURL(url string, responseType string) MsgOption { return func(config *sendConfig) error { @@ -405,6 +503,26 @@ func MsgOptionResponseURL(url string, responseType string) MsgOption { } } +// MsgOptionReplaceOriginal replaces original message with response url +func MsgOptionReplaceOriginal(responseURL string) MsgOption { + return func(config *sendConfig) error { + config.mode = chatResponse + config.endpoint = responseURL + config.replaceOriginal = true + return nil + } +} + +// MsgOptionDeleteOriginal deletes original message with response url +func MsgOptionDeleteOriginal(responseURL string) MsgOption { + return func(config *sendConfig) error { + config.mode = chatResponse + config.endpoint = responseURL + config.deleteOriginal = true + return nil + } +} + // MsgOptionAsUser whether or not to send the message as the user. func MsgOptionAsUser(b bool) MsgOption { return func(config *sendConfig) error { @@ -532,9 +650,9 @@ func MsgOptionBroadcast() MsgOption { // MsgOptionCompose combines multiple options into a single option. func MsgOptionCompose(options ...MsgOption) MsgOption { - return func(c *sendConfig) error { + return func(config *sendConfig) error { for _, opt := range options { - if err := opt(c); err != nil { + if err := opt(config); err != nil { return err } } @@ -544,30 +662,50 @@ func MsgOptionCompose(options ...MsgOption) MsgOption { // MsgOptionParse set parse option. func MsgOptionParse(b bool) MsgOption { - return func(c *sendConfig) error { + return func(config *sendConfig) error { var v string if b { v = "full" } else { v = "none" } - c.values.Set("parse", v) + config.values.Set("parse", v) return nil } } // MsgOptionIconURL sets an icon URL func MsgOptionIconURL(iconURL string) MsgOption { - return func(c *sendConfig) error { - c.values.Set("icon_url", iconURL) + return func(config *sendConfig) error { + config.values.Set("icon_url", iconURL) return nil } } // MsgOptionIconEmoji sets an icon emoji func MsgOptionIconEmoji(iconEmoji string) MsgOption { - return func(c *sendConfig) error { - c.values.Set("icon_emoji", iconEmoji) + return func(config *sendConfig) error { + config.values.Set("icon_emoji", iconEmoji) + return nil + } +} + +// MsgOptionMetadata sets message metadata +func MsgOptionMetadata(metadata SlackMetadata) MsgOption { + return func(config *sendConfig) error { + config.metadata = metadata + meta, err := json.Marshal(metadata) + if err == nil { + config.values.Set("metadata", string(meta)) + } + return err + } +} + +// MsgOptionLinkNames finds and links user groups. Does not support linking individual users +func MsgOptionLinkNames(linkName bool) MsgOption { + return func(config *sendConfig) error { + config.values.Set("link_names", strconv.FormatBool(linkName)) return nil } } @@ -639,25 +777,23 @@ func MsgOptionPostMessageParameters(params PostMessageParameters) MsgOption { } } -// PermalinkParameters are the parameters required to get a permalink to a -// message. Slack documentation can be found here: -// https://api.slack.com/methods/chat.getPermalink +// PermalinkParameters are the parameters required to get a permalink to a message. type PermalinkParameters struct { Channel string Ts string } -// GetPermalink returns the permalink for a message. It takes -// PermalinkParameters and returns a string containing the permalink. It -// returns an error if unable to retrieve the permalink. +// GetPermalink returns the permalink for a message. It takes PermalinkParameters and returns a string containing the +// permalink. It returns an error if unable to retrieve the permalink. +// For more details, see GetPermalinkContext documentation. func (api *Client) GetPermalink(params *PermalinkParameters) (string, error) { return api.GetPermalinkContext(context.Background(), params) } // GetPermalinkContext returns the permalink for a message using a custom context. +// Slack API docs: https://api.slack.com/methods/chat.getPermalink func (api *Client) GetPermalinkContext(ctx context.Context, params *PermalinkParameters) (string, error) { values := url.Values{ - "token": {api.token}, "channel": {params.Channel}, "message_ts": {params.Ts}, } @@ -667,7 +803,7 @@ func (api *Client) GetPermalinkContext(ctx context.Context, params *PermalinkPar Permalink string `json:"permalink"` SlackResponse }{} - err := api.getMethod(ctx, "chat.getPermalink", values, &response) + err := api.getMethod(ctx, "chat.getPermalink", api.token, values, &response) if err != nil { return "", err } @@ -676,25 +812,31 @@ func (api *Client) GetPermalinkContext(ctx context.Context, params *PermalinkPar type GetScheduledMessagesParameters struct { Channel string + TeamID string Cursor string Latest string Limit int Oldest string } -// GetScheduledMessages returns the list of scheduled messages based on params -func (api *Client) GetScheduledMessages(params *GetScheduledMessagesParameters) (channels []Message, nextCursor string, err error) { +// GetScheduledMessages returns the list of scheduled messages based on params. +// For more details, see GetScheduledMessagesContext documentation. +func (api *Client) GetScheduledMessages(params *GetScheduledMessagesParameters) (channels []ScheduledMessage, nextCursor string, err error) { return api.GetScheduledMessagesContext(context.Background(), params) } -// GetScheduledMessagesContext returns the list of scheduled messages in a Slack team with a custom context -func (api *Client) GetScheduledMessagesContext(ctx context.Context, params *GetScheduledMessagesParameters) (channels []Message, nextCursor string, err error) { +// GetScheduledMessagesContext returns the list of scheduled messages based on params with a custom context. +// Slack API docs: https://api.slack.com/methods/chat.getScheduledMessages.list +func (api *Client) GetScheduledMessagesContext(ctx context.Context, params *GetScheduledMessagesParameters) (channels []ScheduledMessage, nextCursor string, err error) { values := url.Values{ "token": {api.token}, } if params.Channel != "" { values.Add("channel", params.Channel) } + if params.TeamID != "" { + values.Add("team_id", params.TeamID) + } if params.Cursor != "" { values.Add("cursor", params.Cursor) } @@ -708,8 +850,8 @@ func (api *Client) GetScheduledMessagesContext(ctx context.Context, params *GetS values.Add("oldest", params.Oldest) } response := struct { - Messages []Message `json:"scheduled_messages"` - ResponseMetaData responseMetaData `json:"response_metadata"` + Messages []ScheduledMessage `json:"scheduled_messages"` + ResponseMetaData responseMetaData `json:"response_metadata"` SlackResponse }{} @@ -727,12 +869,14 @@ type DeleteScheduledMessageParameters struct { AsUser bool } -// DeleteScheduledMessage returns the list of scheduled messages based on params +// DeleteScheduledMessage deletes a pending scheduled message. +// For more details, see DeleteScheduledMessageContext documentation. func (api *Client) DeleteScheduledMessage(params *DeleteScheduledMessageParameters) (bool, error) { return api.DeleteScheduledMessageContext(context.Background(), params) } -// DeleteScheduledMessageContext returns the list of scheduled messages in a Slack team with a custom context +// DeleteScheduledMessageContext deletes a pending scheduled message with a custom context. +// Slack API docs: https://api.slack.com/methods/chat.deleteScheduledMessage func (api *Client) DeleteScheduledMessageContext(ctx context.Context, params *DeleteScheduledMessageParameters) (bool, error) { values := url.Values{ "token": {api.token}, diff --git a/vendor/github.com/slack-go/slack/conversation.go b/vendor/github.com/slack-go/slack/conversation.go index 1e4a61f..bfc17fd 100644 --- a/vendor/github.com/slack-go/slack/conversation.go +++ b/vendor/github.com/slack-go/slack/conversation.go @@ -2,6 +2,7 @@ package slack import ( "context" + "errors" "net/url" "strconv" "strings" @@ -21,14 +22,19 @@ type Conversation struct { IsIM bool `json:"is_im"` IsExtShared bool `json:"is_ext_shared"` IsOrgShared bool `json:"is_org_shared"` + IsGlobalShared bool `json:"is_global_shared"` IsPendingExtShared bool `json:"is_pending_ext_shared"` IsPrivate bool `json:"is_private"` + IsReadOnly bool `json:"is_read_only"` IsMpIM bool `json:"is_mpim"` Unlinked int `json:"unlinked"` NameNormalized string `json:"name_normalized"` NumMembers int `json:"num_members"` Priority float64 `json:"priority"` User string `json:"user"` + ConnectedTeamIDs []string `json:"connected_team_ids,omitempty"` + SharedTeamIDs []string `json:"shared_team_ids,omitempty"` + InternalTeamIDs []string `json:"internal_team_ids,omitempty"` // TODO support pending_shared // TODO support previous_names @@ -59,6 +65,17 @@ type Purpose struct { LastSet JSONTime `json:"last_set"` } +// Properties contains the Canvas associated to the channel. +type Properties struct { + Canvas Canvas `json:"canvas"` +} + +type Canvas struct { + FileId string `json:"file_id"` + IsEmpty bool `json:"is_empty"` + QuipThreadId string `json:"quip_thread_id"` +} + type GetUsersInConversationParameters struct { ChannelID string Cursor string @@ -71,18 +88,21 @@ type GetConversationsForUserParameters struct { Types []string Limit int ExcludeArchived bool + TeamID string } type responseMetaData struct { NextCursor string `json:"next_cursor"` } -// GetUsersInConversation returns the list of users in a conversation +// GetUsersInConversation returns the list of users in a conversation. +// For more details, see GetUsersInConversationContext documentation. func (api *Client) GetUsersInConversation(params *GetUsersInConversationParameters) ([]string, string, error) { return api.GetUsersInConversationContext(context.Background(), params) } -// GetUsersInConversationContext returns the list of users in a conversation with a custom context +// GetUsersInConversationContext returns the list of users in a conversation with a custom context. +// Slack API docs: https://api.slack.com/methods/conversations.members func (api *Client) GetUsersInConversationContext(ctx context.Context, params *GetUsersInConversationParameters) ([]string, string, error) { values := url.Values{ "token": {api.token}, @@ -112,12 +132,14 @@ func (api *Client) GetUsersInConversationContext(ctx context.Context, params *Ge return response.Members, response.ResponseMetaData.NextCursor, nil } -// GetConversationsForUser returns the list conversations for a given user +// GetConversationsForUser returns the list conversations for a given user. +// For more details, see GetConversationsForUserContext documentation. func (api *Client) GetConversationsForUser(params *GetConversationsForUserParameters) (channels []Channel, nextCursor string, err error) { return api.GetConversationsForUserContext(context.Background(), params) } // GetConversationsForUserContext returns the list conversations for a given user with a custom context +// Slack API docs: https://api.slack.com/methods/users.conversations func (api *Client) GetConversationsForUserContext(ctx context.Context, params *GetConversationsForUserParameters) (channels []Channel, nextCursor string, err error) { values := url.Values{ "token": {api.token}, @@ -137,6 +159,10 @@ func (api *Client) GetConversationsForUserContext(ctx context.Context, params *G if params.ExcludeArchived { values.Add("exclude_archived", "true") } + if params.TeamID != "" { + values.Add("team_id", params.TeamID) + } + response := struct { Channels []Channel `json:"channels"` ResponseMetaData responseMetaData `json:"response_metadata"` @@ -150,12 +176,14 @@ func (api *Client) GetConversationsForUserContext(ctx context.Context, params *G return response.Channels, response.ResponseMetaData.NextCursor, response.Err() } -// ArchiveConversation archives a conversation +// ArchiveConversation archives a conversation. +// For more details, see ArchiveConversationContext documentation. func (api *Client) ArchiveConversation(channelID string) error { return api.ArchiveConversationContext(context.Background(), channelID) } -// ArchiveConversationContext archives a conversation with a custom context +// ArchiveConversationContext archives a conversation with a custom context. +// Slack API docs: https://api.slack.com/methods/conversations.archive func (api *Client) ArchiveConversationContext(ctx context.Context, channelID string) error { values := url.Values{ "token": {api.token}, @@ -171,12 +199,14 @@ func (api *Client) ArchiveConversationContext(ctx context.Context, channelID str return response.Err() } -// UnArchiveConversation reverses conversation archival +// UnArchiveConversation reverses conversation archival. +// For more details, see UnArchiveConversationContext documentation. func (api *Client) UnArchiveConversation(channelID string) error { return api.UnArchiveConversationContext(context.Background(), channelID) } -// UnArchiveConversationContext reverses conversation archival with a custom context +// UnArchiveConversationContext reverses conversation archival with a custom context. +// Slack API docs: https://api.slack.com/methods/conversations.unarchive func (api *Client) UnArchiveConversationContext(ctx context.Context, channelID string) error { values := url.Values{ "token": {api.token}, @@ -191,12 +221,14 @@ func (api *Client) UnArchiveConversationContext(ctx context.Context, channelID s return response.Err() } -// SetTopicOfConversation sets the topic for a conversation +// SetTopicOfConversation sets the topic for a conversation. +// For more details, see SetTopicOfConversationContext documentation. func (api *Client) SetTopicOfConversation(channelID, topic string) (*Channel, error) { return api.SetTopicOfConversationContext(context.Background(), channelID, topic) } -// SetTopicOfConversationContext sets the topic for a conversation with a custom context +// SetTopicOfConversationContext sets the topic for a conversation with a custom context. +// Slack API docs: https://api.slack.com/methods/conversations.setTopic func (api *Client) SetTopicOfConversationContext(ctx context.Context, channelID, topic string) (*Channel, error) { values := url.Values{ "token": {api.token}, @@ -215,12 +247,14 @@ func (api *Client) SetTopicOfConversationContext(ctx context.Context, channelID, return response.Channel, response.Err() } -// SetPurposeOfConversation sets the purpose for a conversation +// SetPurposeOfConversation sets the purpose for a conversation. +// For more details, see SetPurposeOfConversationContext documentation. func (api *Client) SetPurposeOfConversation(channelID, purpose string) (*Channel, error) { return api.SetPurposeOfConversationContext(context.Background(), channelID, purpose) } -// SetPurposeOfConversationContext sets the purpose for a conversation with a custom context +// SetPurposeOfConversationContext sets the purpose for a conversation with a custom context. +// Slack API docs: https://api.slack.com/methods/conversations.setPurpose func (api *Client) SetPurposeOfConversationContext(ctx context.Context, channelID, purpose string) (*Channel, error) { values := url.Values{ "token": {api.token}, @@ -240,12 +274,14 @@ func (api *Client) SetPurposeOfConversationContext(ctx context.Context, channelI return response.Channel, response.Err() } -// RenameConversation renames a conversation +// RenameConversation renames a conversation. +// For more details, see RenameConversationContext documentation. func (api *Client) RenameConversation(channelID, channelName string) (*Channel, error) { return api.RenameConversationContext(context.Background(), channelID, channelName) } -// RenameConversationContext renames a conversation with a custom context +// RenameConversationContext renames a conversation with a custom context. +// Slack API docs: https://api.slack.com/methods/conversations.rename func (api *Client) RenameConversationContext(ctx context.Context, channelID, channelName string) (*Channel, error) { values := url.Values{ "token": {api.token}, @@ -265,12 +301,14 @@ func (api *Client) RenameConversationContext(ctx context.Context, channelID, cha return response.Channel, response.Err() } -// InviteUsersToConversation invites users to a channel +// InviteUsersToConversation invites users to a channel. +// For more details, see InviteUsersToConversation documentation. func (api *Client) InviteUsersToConversation(channelID string, users ...string) (*Channel, error) { return api.InviteUsersToConversationContext(context.Background(), channelID, users...) } -// InviteUsersToConversationContext invites users to a channel with a custom context +// InviteUsersToConversationContext invites users to a channel with a custom context. +// Slack API docs: https://api.slack.com/methods/conversations.invite func (api *Client) InviteUsersToConversationContext(ctx context.Context, channelID string, users ...string) (*Channel, error) { values := url.Values{ "token": {api.token}, @@ -290,12 +328,66 @@ func (api *Client) InviteUsersToConversationContext(ctx context.Context, channel return response.Channel, response.Err() } -// KickUserFromConversation removes a user from a conversation +// InviteSharedEmailsToConversation invites users to a shared channels by email. +// For more details, see InviteSharedEmailsToConversationContext documentation. +func (api *Client) InviteSharedEmailsToConversation(channelID string, emails ...string) (string, bool, error) { + return api.inviteSharedToConversationHelper(context.Background(), channelID, emails, nil) +} + +// InviteSharedEmailsToConversationContext invites users to a shared channels by email using context. +// For more details, see inviteSharedToConversationHelper documentation. +func (api *Client) InviteSharedEmailsToConversationContext(ctx context.Context, channelID string, emails ...string) (string, bool, error) { + return api.inviteSharedToConversationHelper(ctx, channelID, emails, nil) +} + +// InviteSharedUserIDsToConversation invites users to a shared channels by user id. +// For more details, see InviteSharedUserIDsToConversationContext documentation. +func (api *Client) InviteSharedUserIDsToConversation(channelID string, userIDs ...string) (string, bool, error) { + return api.inviteSharedToConversationHelper(context.Background(), channelID, nil, userIDs) +} + +// InviteSharedUserIDsToConversationContext invites users to a shared channels by user id with context. +// For more details, see inviteSharedToConversationHelper documentation. +func (api *Client) InviteSharedUserIDsToConversationContext(ctx context.Context, channelID string, userIDs ...string) (string, bool, error) { + return api.inviteSharedToConversationHelper(ctx, channelID, nil, userIDs) +} + +// inviteSharedToConversationHelper invites emails or userIDs to a channel with a custom context. +// This is a helper function for InviteSharedEmailsToConversation and InviteSharedUserIDsToConversation. +// It accepts either emails or userIDs, but not both. +// Slack API docs: https://api.slack.com/methods/conversations.inviteShared +func (api *Client) inviteSharedToConversationHelper(ctx context.Context, channelID string, emails []string, userIDs []string) (string, bool, error) { + values := url.Values{ + "token": {api.token}, + "channel": {channelID}, + } + if len(emails) > 0 { + values.Add("emails", strings.Join(emails, ",")) + } else if len(userIDs) > 0 { + values.Add("user_ids", strings.Join(userIDs, ",")) + } + response := struct { + SlackResponse + InviteID string `json:"invite_id"` + IsLegacySharedChannel bool `json:"is_legacy_shared_channel"` + }{} + + err := api.postMethod(ctx, "conversations.inviteShared", values, &response) + if err != nil { + return "", false, err + } + + return response.InviteID, response.IsLegacySharedChannel, response.Err() +} + +// KickUserFromConversation removes a user from a conversation. +// For more details, see KickUserFromConversationContext documentation. func (api *Client) KickUserFromConversation(channelID string, user string) error { return api.KickUserFromConversationContext(context.Background(), channelID, user) } -// KickUserFromConversationContext removes a user from a conversation with a custom context +// KickUserFromConversationContext removes a user from a conversation with a custom context. +// Slack API docs: https://api.slack.com/methods/conversations.kick func (api *Client) KickUserFromConversationContext(ctx context.Context, channelID string, user string) error { values := url.Values{ "token": {api.token}, @@ -312,12 +404,14 @@ func (api *Client) KickUserFromConversationContext(ctx context.Context, channelI return response.Err() } -// CloseConversation closes a direct message or multi-person direct message +// CloseConversation closes a direct message or multi-person direct message. +// For more details, see CloseConversationContext documentation. func (api *Client) CloseConversation(channelID string) (noOp bool, alreadyClosed bool, err error) { return api.CloseConversationContext(context.Background(), channelID) } -// CloseConversationContext closes a direct message or multi-person direct message with a custom context +// CloseConversationContext closes a direct message or multi-person direct message with a custom context. +// Slack API docs: https://api.slack.com/methods/conversations.close func (api *Client) CloseConversationContext(ctx context.Context, channelID string) (noOp bool, alreadyClosed bool, err error) { values := url.Values{ "token": {api.token}, @@ -337,17 +431,28 @@ func (api *Client) CloseConversationContext(ctx context.Context, channelID strin return response.NoOp, response.AlreadyClosed, response.Err() } -// CreateConversation initiates a public or private channel-based conversation -func (api *Client) CreateConversation(channelName string, isPrivate bool) (*Channel, error) { - return api.CreateConversationContext(context.Background(), channelName, isPrivate) +type CreateConversationParams struct { + ChannelName string + IsPrivate bool + TeamID string +} + +// CreateConversation initiates a public or private channel-based conversation. +// For more details, see CreateConversationContext documentation. +func (api *Client) CreateConversation(params CreateConversationParams) (*Channel, error) { + return api.CreateConversationContext(context.Background(), params) } -// CreateConversationContext initiates a public or private channel-based conversation with a custom context -func (api *Client) CreateConversationContext(ctx context.Context, channelName string, isPrivate bool) (*Channel, error) { +// CreateConversationContext initiates a public or private channel-based conversation with a custom context. +// Slack API docs: https://api.slack.com/methods/conversations.create +func (api *Client) CreateConversationContext(ctx context.Context, params CreateConversationParams) (*Channel, error) { values := url.Values{ "token": {api.token}, - "name": {channelName}, - "is_private": {strconv.FormatBool(isPrivate)}, + "name": {params.ChannelName}, + "is_private": {strconv.FormatBool(params.IsPrivate)}, + } + if params.TeamID != "" { + values.Set("team_id", params.TeamID) } response, err := api.channelRequest(ctx, "conversations.create", values) if err != nil { @@ -357,17 +462,35 @@ func (api *Client) CreateConversationContext(ctx context.Context, channelName st return &response.Channel, nil } -// GetConversationInfo retrieves information about a conversation -func (api *Client) GetConversationInfo(channelID string, includeLocale bool) (*Channel, error) { - return api.GetConversationInfoContext(context.Background(), channelID, includeLocale) +// GetConversationInfoInput Defines the parameters of a GetConversationInfo and GetConversationInfoContext function +type GetConversationInfoInput struct { + ChannelID string + IncludeLocale bool + IncludeNumMembers bool +} + +// GetConversationInfo retrieves information about a conversation. +// For more details, see GetConversationInfoContext documentation. +func (api *Client) GetConversationInfo(input *GetConversationInfoInput) (*Channel, error) { + return api.GetConversationInfoContext(context.Background(), input) } -// GetConversationInfoContext retrieves information about a conversation with a custom context -func (api *Client) GetConversationInfoContext(ctx context.Context, channelID string, includeLocale bool) (*Channel, error) { +// GetConversationInfoContext retrieves information about a conversation with a custom context. +// Slack API docs: https://api.slack.com/methods/conversations.info +func (api *Client) GetConversationInfoContext(ctx context.Context, input *GetConversationInfoInput) (*Channel, error) { + if input == nil { + return nil, errors.New("GetConversationInfoInput must not be nil") + } + + if input.ChannelID == "" { + return nil, errors.New("ChannelID must be defined") + } + values := url.Values{ - "token": {api.token}, - "channel": {channelID}, - "include_locale": {strconv.FormatBool(includeLocale)}, + "token": {api.token}, + "channel": {input.ChannelID}, + "include_locale": {strconv.FormatBool(input.IncludeLocale)}, + "include_num_members": {strconv.FormatBool(input.IncludeNumMembers)}, } response, err := api.channelRequest(ctx, "conversations.info", values) if err != nil { @@ -377,12 +500,14 @@ func (api *Client) GetConversationInfoContext(ctx context.Context, channelID str return &response.Channel, response.Err() } -// LeaveConversation leaves a conversation +// LeaveConversation leaves a conversation. +// For more details, see LeaveConversationContext documentation. func (api *Client) LeaveConversation(channelID string) (bool, error) { return api.LeaveConversationContext(context.Background(), channelID) } -// LeaveConversationContext leaves a conversation with a custom context +// LeaveConversationContext leaves a conversation with a custom context. +// Slack API docs: https://api.slack.com/methods/conversations.leave func (api *Client) LeaveConversationContext(ctx context.Context, channelID string) (bool, error) { values := url.Values{ "token": {api.token}, @@ -398,21 +523,24 @@ func (api *Client) LeaveConversationContext(ctx context.Context, channelID strin } type GetConversationRepliesParameters struct { - ChannelID string - Timestamp string - Cursor string - Inclusive bool - Latest string - Limit int - Oldest string -} - -// GetConversationReplies retrieves a thread of messages posted to a conversation + ChannelID string + Timestamp string + Cursor string + Inclusive bool + Latest string + Limit int + Oldest string + IncludeAllMetadata bool +} + +// GetConversationReplies retrieves a thread of messages posted to a conversation. +// For more details, see GetConversationRepliesContext documentation. func (api *Client) GetConversationReplies(params *GetConversationRepliesParameters) (msgs []Message, hasMore bool, nextCursor string, err error) { return api.GetConversationRepliesContext(context.Background(), params) } -// GetConversationRepliesContext retrieves a thread of messages posted to a conversation with a custom context +// GetConversationRepliesContext retrieves a thread of messages posted to a conversation with a custom context. +// Slack API docs: https://api.slack.com/methods/conversations.replies func (api *Client) GetConversationRepliesContext(ctx context.Context, params *GetConversationRepliesParameters) (msgs []Message, hasMore bool, nextCursor string, err error) { values := url.Values{ "token": {api.token}, @@ -436,6 +564,11 @@ func (api *Client) GetConversationRepliesContext(ctx context.Context, params *Ge } else { values.Add("inclusive", "0") } + if params.IncludeAllMetadata { + values.Add("include_all_metadata", "1") + } else { + values.Add("include_all_metadata", "0") + } response := struct { SlackResponse HasMore bool `json:"has_more"` @@ -455,21 +588,23 @@ func (api *Client) GetConversationRepliesContext(ctx context.Context, params *Ge type GetConversationsParameters struct { Cursor string - ExcludeArchived string + ExcludeArchived bool Limit int Types []string + TeamID string } -// GetConversations returns the list of channels in a Slack team +// GetConversations returns the list of channels in a Slack team. +// For more details, see GetConversationsContext documentation. func (api *Client) GetConversations(params *GetConversationsParameters) (channels []Channel, nextCursor string, err error) { return api.GetConversationsContext(context.Background(), params) } -// GetConversationsContext returns the list of channels in a Slack team with a custom context +// GetConversationsContext returns the list of channels in a Slack team with a custom context. +// Slack API docs: https://api.slack.com/methods/conversations.list func (api *Client) GetConversationsContext(ctx context.Context, params *GetConversationsParameters) (channels []Channel, nextCursor string, err error) { values := url.Values{ - "token": {api.token}, - "exclude_archived": {params.ExcludeArchived}, + "token": {api.token}, } if params.Cursor != "" { values.Add("cursor", params.Cursor) @@ -480,6 +615,13 @@ func (api *Client) GetConversationsContext(ctx context.Context, params *GetConve if params.Types != nil { values.Add("types", strings.Join(params.Types, ",")) } + if params.ExcludeArchived { + values.Add("exclude_archived", strconv.FormatBool(params.ExcludeArchived)) + } + if params.TeamID != "" { + values.Add("team_id", params.TeamID) + } + response := struct { Channels []Channel `json:"channels"` ResponseMetaData responseMetaData `json:"response_metadata"` @@ -500,12 +642,14 @@ type OpenConversationParameters struct { Users []string } -// OpenConversation opens or resumes a direct message or multi-person direct message +// OpenConversation opens or resumes a direct message or multi-person direct message. +// For more details, see OpenConversationContext documentation. func (api *Client) OpenConversation(params *OpenConversationParameters) (*Channel, bool, bool, error) { return api.OpenConversationContext(context.Background(), params) } -// OpenConversationContext opens or resumes a direct message or multi-person direct message with a custom context +// OpenConversationContext opens or resumes a direct message or multi-person direct message with a custom context. +// Slack API docs: https://api.slack.com/methods/conversations.open func (api *Client) OpenConversationContext(ctx context.Context, params *OpenConversationParameters) (*Channel, bool, bool, error) { values := url.Values{ "token": {api.token}, @@ -532,12 +676,14 @@ func (api *Client) OpenConversationContext(ctx context.Context, params *OpenConv return response.Channel, response.NoOp, response.AlreadyOpen, response.Err() } -// JoinConversation joins an existing conversation +// JoinConversation joins an existing conversation. +// For more details, see JoinConversationContext documentation. func (api *Client) JoinConversation(channelID string) (*Channel, string, []string, error) { return api.JoinConversationContext(context.Background(), channelID) } -// JoinConversationContext joins an existing conversation with a custom context +// JoinConversationContext joins an existing conversation with a custom context. +// Slack API docs: https://api.slack.com/methods/conversations.join func (api *Client) JoinConversationContext(ctx context.Context, channelID string) (*Channel, string, []string, error) { values := url.Values{"token": {api.token}, "channel": {channelID}} response := struct { @@ -564,12 +710,13 @@ func (api *Client) JoinConversationContext(ctx context.Context, channelID string } type GetConversationHistoryParameters struct { - ChannelID string - Cursor string - Inclusive bool - Latest string - Limit int - Oldest string + ChannelID string + Cursor string + Inclusive bool + Latest string + Limit int + Oldest string + IncludeAllMetadata bool } type GetConversationHistoryResponse struct { @@ -583,12 +730,14 @@ type GetConversationHistoryResponse struct { Messages []Message `json:"messages"` } -// GetConversationHistory joins an existing conversation +// GetConversationHistory joins an existing conversation. +// For more details, see GetConversationHistoryContext documentation. func (api *Client) GetConversationHistory(params *GetConversationHistoryParameters) (*GetConversationHistoryResponse, error) { return api.GetConversationHistoryContext(context.Background(), params) } -// GetConversationHistoryContext joins an existing conversation with a custom context +// GetConversationHistoryContext joins an existing conversation with a custom context. +// Slack API docs: https://api.slack.com/methods/conversations.history func (api *Client) GetConversationHistoryContext(ctx context.Context, params *GetConversationHistoryParameters) (*GetConversationHistoryResponse, error) { values := url.Values{"token": {api.token}, "channel": {params.ChannelID}} if params.Cursor != "" { @@ -608,6 +757,11 @@ func (api *Client) GetConversationHistoryContext(ctx context.Context, params *Ge if params.Oldest != "" { values.Add("oldest", params.Oldest) } + if params.IncludeAllMetadata { + values.Add("include_all_metadata", "1") + } else { + values.Add("include_all_metadata", "0") + } response := GetConversationHistoryResponse{} @@ -618,3 +772,27 @@ func (api *Client) GetConversationHistoryContext(ctx context.Context, params *Ge return &response, response.Err() } + +// MarkConversation sets the read mark of a conversation to a specific point. +// For more details, see MarkConversationContext documentation. +func (api *Client) MarkConversation(channel, ts string) (err error) { + return api.MarkConversationContext(context.Background(), channel, ts) +} + +// MarkConversationContext sets the read mark of a conversation to a specific point with a custom context. +// Slack API docs: https://api.slack.com/methods/conversations.mark +func (api *Client) MarkConversationContext(ctx context.Context, channel, ts string) error { + values := url.Values{ + "token": {api.token}, + "channel": {channel}, + "ts": {ts}, + } + + response := &SlackResponse{} + + err := api.postMethod(ctx, "conversations.mark", values, response) + if err != nil { + return err + } + return response.Err() +} diff --git a/vendor/github.com/slack-go/slack/dialog.go b/vendor/github.com/slack-go/slack/dialog.go index 376cd9e..f94113f 100644 --- a/vendor/github.com/slack-go/slack/dialog.go +++ b/vendor/github.com/slack-go/slack/dialog.go @@ -54,7 +54,9 @@ type DialogCallback InteractionCallback // DialogSubmissionCallback is sent from Slack when a user submits a form from within a dialog type DialogSubmissionCallback struct { - State string `json:"state,omitempty"` + // NOTE: State is only used with the dialog_submission type. + // You should use InteractionCallback.BlockActionsState for block_actions type. + State string `json:"-"` Submission map[string]string `json:"submission"` } diff --git a/vendor/github.com/slack-go/slack/dialog_select.go b/vendor/github.com/slack-go/slack/dialog_select.go index 385cef6..3d6be98 100644 --- a/vendor/github.com/slack-go/slack/dialog_select.go +++ b/vendor/github.com/slack-go/slack/dialog_select.go @@ -54,6 +54,20 @@ func NewStaticSelectDialogInput(name, label string, options []DialogSelectOption } } +// NewExternalSelectDialogInput constructor for a `external` datasource menu input +func NewExternalSelectDialogInput(name, label string, options []DialogSelectOption) *DialogInputSelect { + return &DialogInputSelect{ + DialogInput: DialogInput{ + Type: InputTypeSelect, + Name: name, + Label: label, + Optional: true, + }, + DataSource: DialogDataSourceExternal, + Options: options, + } +} + // NewGroupedSelectDialogInput creates grouped options select input for Dialogs. func NewGroupedSelectDialogInput(name, label string, options []DialogOptionGroup) *DialogInputSelect { return &DialogInputSelect{ diff --git a/vendor/github.com/slack-go/slack/dialog_text.go b/vendor/github.com/slack-go/slack/dialog_text.go index da06bd6..25fa1b6 100644 --- a/vendor/github.com/slack-go/slack/dialog_text.go +++ b/vendor/github.com/slack-go/slack/dialog_text.go @@ -18,7 +18,7 @@ const ( ) // TextInputElement subtype of DialogInput -// https://api.slack.com/dialogs#option_element_attributes#text_element_attributes +// https://api.slack.com/dialogs#option_element_attributes#text_element_attributes type TextInputElement struct { DialogInput MaxLength int `json:"max_length,omitempty"` diff --git a/vendor/github.com/slack-go/slack/dnd.go b/vendor/github.com/slack-go/slack/dnd.go index a3aa680..81eaf50 100644 --- a/vendor/github.com/slack-go/slack/dnd.go +++ b/vendor/github.com/slack-go/slack/dnd.go @@ -45,12 +45,14 @@ func (api *Client) dndRequest(ctx context.Context, path string, values url.Value return response, response.Err() } -// EndDND ends the user's scheduled Do Not Disturb session +// EndDND ends the user's scheduled Do Not Disturb session. +// For more information see the EndDNDContext documentation. func (api *Client) EndDND() error { return api.EndDNDContext(context.Background()) } -// EndDNDContext ends the user's scheduled Do Not Disturb session with a custom context +// EndDNDContext ends the user's scheduled Do Not Disturb session with a custom context. +// Slack API docs: https://api.slack.com/methods/dnd.endDnd func (api *Client) EndDNDContext(ctx context.Context) error { values := url.Values{ "token": {api.token}, @@ -65,12 +67,14 @@ func (api *Client) EndDNDContext(ctx context.Context) error { return response.Err() } -// EndSnooze ends the current user's snooze mode +// EndSnooze ends the current user's snooze mode. +// For more information see the EndSnoozeContext documentation. func (api *Client) EndSnooze() (*DNDStatus, error) { return api.EndSnoozeContext(context.Background()) } -// EndSnoozeContext ends the current user's snooze mode with a custom context +// EndSnoozeContext ends the current user's snooze mode with a custom context. +// Slack API docs: https://api.slack.com/methods/dnd.endSnooze func (api *Client) EndSnoozeContext(ctx context.Context) (*DNDStatus, error) { values := url.Values{ "token": {api.token}, @@ -84,11 +88,13 @@ func (api *Client) EndSnoozeContext(ctx context.Context) (*DNDStatus, error) { } // GetDNDInfo provides information about a user's current Do Not Disturb settings. +// For more information see the GetDNDInfoContext documentation. func (api *Client) GetDNDInfo(user *string) (*DNDStatus, error) { return api.GetDNDInfoContext(context.Background(), user) } // GetDNDInfoContext provides information about a user's current Do Not Disturb settings with a custom context. +// Slack API docs: https://api.slack.com/methods/dnd.info func (api *Client) GetDNDInfoContext(ctx context.Context, user *string) (*DNDStatus, error) { values := url.Values{ "token": {api.token}, @@ -105,11 +111,13 @@ func (api *Client) GetDNDInfoContext(ctx context.Context, user *string) (*DNDSta } // GetDNDTeamInfo provides information about a user's current Do Not Disturb settings. +// For more information see the GetDNDTeamInfoContext documentation. func (api *Client) GetDNDTeamInfo(users []string) (map[string]DNDStatus, error) { return api.GetDNDTeamInfoContext(context.Background(), users) } // GetDNDTeamInfoContext provides information about a user's current Do Not Disturb settings with a custom context. +// Slack API docs: https://api.slack.com/methods/dnd.teamInfo func (api *Client) GetDNDTeamInfoContext(ctx context.Context, users []string) (map[string]DNDStatus, error) { values := url.Values{ "token": {api.token}, @@ -128,15 +136,16 @@ func (api *Client) GetDNDTeamInfoContext(ctx context.Context, users []string) (m return response.Users, nil } -// SetSnooze adjusts the snooze duration for a user's Do Not Disturb -// settings. If a snooze session is not already active for the user, invoking -// this method will begin one for the specified duration. +// SetSnooze adjusts the snooze duration for a user's Do Not Disturb settings. +// For more information see the SetSnoozeContext documentation. func (api *Client) SetSnooze(minutes int) (*DNDStatus, error) { return api.SetSnoozeContext(context.Background(), minutes) } -// SetSnoozeContext adjusts the snooze duration for a user's Do Not Disturb settings with a custom context. -// For more information see the SetSnooze docs +// SetSnoozeContext adjusts the snooze duration for a user's Do Not Disturb settings. +// If a snooze session is not already active for the user, invoking this method will +// begin one for the specified duration. +// Slack API docs: https://api.slack.com/methods/dnd.setSnooze func (api *Client) SetSnoozeContext(ctx context.Context, minutes int) (*DNDStatus, error) { values := url.Values{ "token": {api.token}, diff --git a/vendor/github.com/slack-go/slack/emoji.go b/vendor/github.com/slack-go/slack/emoji.go index b2b0c6c..139df0f 100644 --- a/vendor/github.com/slack-go/slack/emoji.go +++ b/vendor/github.com/slack-go/slack/emoji.go @@ -10,12 +10,14 @@ type emojiResponseFull struct { SlackResponse } -// GetEmoji retrieves all the emojis +// GetEmoji retrieves all the emojis. +// For more details see GetEmojiContext documentation. func (api *Client) GetEmoji() (map[string]string, error) { return api.GetEmojiContext(context.Background()) } -// GetEmojiContext retrieves all the emojis with a custom context +// GetEmojiContext retrieves all the emojis with a custom context. +// Slack API docs: https://api.slack.com/methods/emoji.list func (api *Client) GetEmojiContext(ctx context.Context) (map[string]string, error) { values := url.Values{ "token": {api.token}, diff --git a/vendor/github.com/slack-go/slack/errors.go b/vendor/github.com/slack-go/slack/errors.go index a1dfec2..8be22a6 100644 --- a/vendor/github.com/slack-go/slack/errors.go +++ b/vendor/github.com/slack-go/slack/errors.go @@ -9,6 +9,7 @@ const ( ErrRTMGoodbye = errorsx.String("goodbye detected") ErrRTMDeadman = errorsx.String("deadman switch triggered") ErrParametersMissing = errorsx.String("received empty parameters") + ErrBlockIDNotUnique = errorsx.String("Block ID needs to be unique") ErrInvalidConfiguration = errorsx.String("invalid configuration") ErrMissingHeaders = errorsx.String("missing headers") ErrExpiredTimestamp = errorsx.String("timestamp is too old") diff --git a/vendor/github.com/slack-go/slack/files.go b/vendor/github.com/slack-go/slack/files.go index 3a7363d..6844edd 100644 --- a/vendor/github.com/slack-go/slack/files.go +++ b/vendor/github.com/slack-go/slack/files.go @@ -2,6 +2,7 @@ package slack import ( "context" + "encoding/json" "fmt" "io" "net/url" @@ -10,14 +11,15 @@ import ( ) const ( - // Add here the defaults in the siten - DEFAULT_FILES_USER = "" - DEFAULT_FILES_CHANNEL = "" - DEFAULT_FILES_TS_FROM = 0 - DEFAULT_FILES_TS_TO = -1 - DEFAULT_FILES_TYPES = "all" - DEFAULT_FILES_COUNT = 100 - DEFAULT_FILES_PAGE = 1 + // Add here the defaults in the site + DEFAULT_FILES_USER = "" + DEFAULT_FILES_CHANNEL = "" + DEFAULT_FILES_TS_FROM = 0 + DEFAULT_FILES_TS_TO = -1 + DEFAULT_FILES_TYPES = "all" + DEFAULT_FILES_COUNT = 100 + DEFAULT_FILES_PAGE = 1 + DEFAULT_FILES_SHOW_HIDDEN = false ) // File contains all the information for a file @@ -127,11 +129,13 @@ type FileUploadParameters struct { type GetFilesParameters struct { User string Channel string + TeamID string TimestampFrom JSONTime TimestampTo JSONTime Types string Count int Page int + ShowHidden bool } // ListFilesParameters contains all the parameters necessary (including the optional ones) for a ListFiles() request @@ -139,10 +143,63 @@ type ListFilesParameters struct { Limit int User string Channel string + TeamID string Types string Cursor string } +type UploadFileV2Parameters struct { + File string + FileSize int + Content string + Reader io.Reader + Filename string + Title string + InitialComment string + Channel string + ThreadTimestamp string + AltTxt string + SnippetText string +} + +type getUploadURLExternalParameters struct { + altText string + fileSize int + fileName string + snippetText string +} + +type getUploadURLExternalResponse struct { + UploadURL string `json:"upload_url"` + FileID string `json:"file_id"` + SlackResponse +} + +type uploadToURLParameters struct { + UploadURL string + Reader io.Reader + File string + Content string + Filename string +} + +type FileSummary struct { + ID string `json:"id"` + Title string `json:"title"` +} + +type completeUploadExternalParameters struct { + title string + channel string + initialComment string + threadTimestamp string +} + +type completeUploadExternalResponse struct { + SlackResponse + Files []FileSummary `json:"files"` +} + type fileResponseFull struct { File `json:"file"` Paging `json:"paging"` @@ -163,6 +220,7 @@ func NewGetFilesParameters() GetFilesParameters { Types: DEFAULT_FILES_TYPES, Count: DEFAULT_FILES_COUNT, Page: DEFAULT_FILES_PAGE, + ShowHidden: DEFAULT_FILES_SHOW_HIDDEN, } } @@ -176,12 +234,14 @@ func (api *Client) fileRequest(ctx context.Context, path string, values url.Valu return response, response.Err() } -// GetFileInfo retrieves a file and related comments +// GetFileInfo retrieves a file and related comments. +// For more details, see GetFileInfoContext documentation. func (api *Client) GetFileInfo(fileID string, count, page int) (*File, []Comment, *Paging, error) { return api.GetFileInfoContext(context.Background(), fileID, count, page) } -// GetFileInfoContext retrieves a file and related comments with a custom context +// GetFileInfoContext retrieves a file and related comments with a custom context. +// Slack API docs: https://api.slack.com/methods/files.info func (api *Client) GetFileInfoContext(ctx context.Context, fileID string, count, page int) (*File, []Comment, *Paging, error) { values := url.Values{ "token": {api.token}, @@ -197,101 +257,122 @@ func (api *Client) GetFileInfoContext(ctx context.Context, fileID string, count, return &response.File, response.Comments, &response.Paging, nil } -// GetFile retreives a given file from its private download URL +// GetFile retrieves a given file from its private download URL. func (api *Client) GetFile(downloadURL string, writer io.Writer) error { - return downloadFile(api.httpclient, api.token, downloadURL, writer, api) + return api.GetFileContext(context.Background(), downloadURL, writer) } -// GetFiles retrieves all files according to the parameters given -func (api *Client) GetFiles(params GetFilesParameters) ([]File, *Paging, error) { - return api.GetFilesContext(context.Background(), params) +// GetFileContext retrieves a given file from its private download URL with a custom context. +// For more details, see GetFile documentation. +func (api *Client) GetFileContext(ctx context.Context, downloadURL string, writer io.Writer) error { + return downloadFile(ctx, api.httpclient, api.token, downloadURL, writer, api) } -// ListFiles retrieves all files according to the parameters given. Uses cursor based pagination. -func (api *Client) ListFiles(params ListFilesParameters) ([]File, *ListFilesParameters, error) { - return api.ListFilesContext(context.Background(), params) +// GetFiles retrieves all files according to the parameters given. +// For more details, see GetFilesContext documentation. +func (api *Client) GetFiles(params GetFilesParameters) ([]File, *Paging, error) { + return api.GetFilesContext(context.Background(), params) } -// ListFilesContext retrieves all files according to the parameters given with a custom context. Uses cursor based pagination. -func (api *Client) ListFilesContext(ctx context.Context, params ListFilesParameters) ([]File, *ListFilesParameters, error) { +// GetFilesContext retrieves all files according to the parameters given with a custom context. +// Slack API docs: https://api.slack.com/methods/files.list +func (api *Client) GetFilesContext(ctx context.Context, params GetFilesParameters) ([]File, *Paging, error) { values := url.Values{ "token": {api.token}, } - if params.User != DEFAULT_FILES_USER { values.Add("user", params.User) } if params.Channel != DEFAULT_FILES_CHANNEL { values.Add("channel", params.Channel) } - if params.Limit != DEFAULT_FILES_COUNT { - values.Add("limit", strconv.Itoa(params.Limit)) + if params.TeamID != "" { + values.Add("team_id", params.TeamID) } - if params.Cursor != "" { - values.Add("cursor", params.Cursor) + if params.TimestampFrom != DEFAULT_FILES_TS_FROM { + values.Add("ts_from", strconv.FormatInt(int64(params.TimestampFrom), 10)) + } + if params.TimestampTo != DEFAULT_FILES_TS_TO { + values.Add("ts_to", strconv.FormatInt(int64(params.TimestampTo), 10)) + } + if params.Types != DEFAULT_FILES_TYPES { + values.Add("types", params.Types) + } + if params.Count != DEFAULT_FILES_COUNT { + values.Add("count", strconv.Itoa(params.Count)) + } + if params.Page != DEFAULT_FILES_PAGE { + values.Add("page", strconv.Itoa(params.Page)) + } + if params.ShowHidden != DEFAULT_FILES_SHOW_HIDDEN { + values.Add("show_files_hidden_by_limit", strconv.FormatBool(params.ShowHidden)) } response, err := api.fileRequest(ctx, "files.list", values) if err != nil { return nil, nil, err } + return response.Files, &response.Paging, nil +} - params.Cursor = response.Metadata.Cursor - - return response.Files, ¶ms, nil +// ListFiles retrieves all files according to the parameters given. Uses cursor based pagination. +// For more details, see ListFilesContext documentation. +func (api *Client) ListFiles(params ListFilesParameters) ([]File, *ListFilesParameters, error) { + return api.ListFilesContext(context.Background(), params) } -// GetFilesContext retrieves all files according to the parameters given with a custom context -func (api *Client) GetFilesContext(ctx context.Context, params GetFilesParameters) ([]File, *Paging, error) { +// ListFilesContext retrieves all files according to the parameters given with a custom context. +// Slack API docs: https://api.slack.com/methods/files.list +func (api *Client) ListFilesContext(ctx context.Context, params ListFilesParameters) ([]File, *ListFilesParameters, error) { values := url.Values{ "token": {api.token}, } + if params.User != DEFAULT_FILES_USER { values.Add("user", params.User) } if params.Channel != DEFAULT_FILES_CHANNEL { values.Add("channel", params.Channel) } - if params.TimestampFrom != DEFAULT_FILES_TS_FROM { - values.Add("ts_from", strconv.FormatInt(int64(params.TimestampFrom), 10)) - } - if params.TimestampTo != DEFAULT_FILES_TS_TO { - values.Add("ts_to", strconv.FormatInt(int64(params.TimestampTo), 10)) - } - if params.Types != DEFAULT_FILES_TYPES { - values.Add("types", params.Types) + if params.TeamID != "" { + values.Add("team_id", params.TeamID) } - if params.Count != DEFAULT_FILES_COUNT { - values.Add("count", strconv.Itoa(params.Count)) + if params.Limit != DEFAULT_FILES_COUNT { + values.Add("limit", strconv.Itoa(params.Limit)) } - if params.Page != DEFAULT_FILES_PAGE { - values.Add("page", strconv.Itoa(params.Page)) + if params.Cursor != "" { + values.Add("cursor", params.Cursor) } response, err := api.fileRequest(ctx, "files.list", values) if err != nil { return nil, nil, err } - return response.Files, &response.Paging, nil + + params.Cursor = response.Metadata.Cursor + + return response.Files, ¶ms, nil } -// UploadFile uploads a file +// UploadFile uploads a file. +// DEPRECATED: Use UploadFileV2 instead. This will stop functioning on March 11, 2025. +// For more details, see: https://api.slack.com/methods/files.upload#markdown func (api *Client) UploadFile(params FileUploadParameters) (file *File, err error) { return api.UploadFileContext(context.Background(), params) } -// UploadFileContext uploads a file and setting a custom context +// UploadFileContext uploads a file and setting a custom context. +// DEPRECATED: Use UploadFileV2Context instead. This will stop functioning on March 11, 2025. +// For more details, see: https://api.slack.com/methods/files.upload#markdown func (api *Client) UploadFileContext(ctx context.Context, params FileUploadParameters) (file *File, err error) { // Test if user token is valid. This helps because client.Do doesn't like this for some reason. XXX: More // investigation needed, but for now this will do. - _, err = api.AuthTest() + _, err = api.AuthTestContext(ctx) if err != nil { return nil, err } response := &fileResponseFull{} - values := url.Values{ - "token": {api.token}, - } + values := url.Values{} if params.Filetype != "" { values.Add("filetype", params.Filetype) } @@ -312,14 +393,15 @@ func (api *Client) UploadFileContext(ctx context.Context, params FileUploadParam } if params.Content != "" { values.Add("content", params.Content) + values.Add("token", api.token) err = api.postMethod(ctx, "files.upload", values, response) } else if params.File != "" { - err = postLocalWithMultipartResponse(ctx, api.httpclient, api.endpoint+"files.upload", params.File, "file", values, response, api) + err = postLocalWithMultipartResponse(ctx, api.httpclient, api.endpoint+"files.upload", params.File, "file", api.token, values, response, api) } else if params.Reader != nil { if params.Filename == "" { return nil, fmt.Errorf("files.upload: FileUploadParameters.Filename is mandatory when using FileUploadParameters.Reader") } - err = postWithMultipartResponse(ctx, api.httpclient, api.endpoint+"files.upload", params.Filename, "file", values, params.Reader, response, api) + err = postWithMultipartResponse(ctx, api.httpclient, api.endpoint+"files.upload", params.Filename, "file", api.token, values, params.Reader, response, api) } if err != nil { @@ -329,12 +411,14 @@ func (api *Client) UploadFileContext(ctx context.Context, params FileUploadParam return &response.File, response.Err() } -// DeleteFileComment deletes a file's comment +// DeleteFileComment deletes a file's comment. +// For more details, see DeleteFileCommentContext documentation. func (api *Client) DeleteFileComment(commentID, fileID string) error { return api.DeleteFileCommentContext(context.Background(), fileID, commentID) } -// DeleteFileCommentContext deletes a file's comment with a custom context +// DeleteFileCommentContext deletes a file's comment with a custom context. +// Slack API docs: https://api.slack.com/methods/files.comments.delete func (api *Client) DeleteFileCommentContext(ctx context.Context, fileID, commentID string) (err error) { if fileID == "" || commentID == "" { return ErrParametersMissing @@ -349,12 +433,14 @@ func (api *Client) DeleteFileCommentContext(ctx context.Context, fileID, comment return err } -// DeleteFile deletes a file +// DeleteFile deletes a file. +// For more details, see DeleteFileContext documentation. func (api *Client) DeleteFile(fileID string) error { return api.DeleteFileContext(context.Background(), fileID) } -// DeleteFileContext deletes a file with a custom context +// DeleteFileContext deletes a file with a custom context. +// Slack API docs: https://api.slack.com/methods/files.delete func (api *Client) DeleteFileContext(ctx context.Context, fileID string) (err error) { values := url.Values{ "token": {api.token}, @@ -365,12 +451,14 @@ func (api *Client) DeleteFileContext(ctx context.Context, fileID string) (err er return err } -// RevokeFilePublicURL disables public/external sharing for a file +// RevokeFilePublicURL disables public/external sharing for a file. +// For more details, see RevokeFilePublicURLContext documentation. func (api *Client) RevokeFilePublicURL(fileID string) (*File, error) { return api.RevokeFilePublicURLContext(context.Background(), fileID) } -// RevokeFilePublicURLContext disables public/external sharing for a file with a custom context +// RevokeFilePublicURLContext disables public/external sharing for a file with a custom context. +// Slack API docs: https://api.slack.com/methods/files.revokePublicURL func (api *Client) RevokeFilePublicURLContext(ctx context.Context, fileID string) (*File, error) { values := url.Values{ "token": {api.token}, @@ -384,12 +472,14 @@ func (api *Client) RevokeFilePublicURLContext(ctx context.Context, fileID string return &response.File, nil } -// ShareFilePublicURL enabled public/external sharing for a file +// ShareFilePublicURL enabled public/external sharing for a file. +// For more details, see ShareFilePublicURLContext documentation. func (api *Client) ShareFilePublicURL(fileID string) (*File, []Comment, *Paging, error) { return api.ShareFilePublicURLContext(context.Background(), fileID) } -// ShareFilePublicURLContext enabled public/external sharing for a file with a custom context +// ShareFilePublicURLContext enabled public/external sharing for a file with a custom context. +// Slack API docs: https://api.slack.com/methods/files.sharedPublicURL func (api *Client) ShareFilePublicURLContext(ctx context.Context, fileID string) (*File, []Comment, *Paging, error) { values := url.Values{ "token": {api.token}, @@ -402,3 +492,128 @@ func (api *Client) ShareFilePublicURLContext(ctx context.Context, fileID string) } return &response.File, response.Comments, &response.Paging, nil } + +// getUploadURLExternal gets a URL and fileID from slack which can later be used to upload a file. +func (api *Client) getUploadURLExternal(ctx context.Context, params getUploadURLExternalParameters) (*getUploadURLExternalResponse, error) { + values := url.Values{ + "token": {api.token}, + "filename": {params.fileName}, + "length": {strconv.Itoa(params.fileSize)}, + } + if params.altText != "" { + values.Add("initial_comment", params.altText) + } + if params.snippetText != "" { + values.Add("thread_ts", params.snippetText) + } + response := &getUploadURLExternalResponse{} + err := api.postMethod(ctx, "files.getUploadURLExternal", values, response) + if err != nil { + return nil, err + } + + return response, response.Err() +} + +// uploadToURL uploads the file to the provided URL using post method +func (api *Client) uploadToURL(ctx context.Context, params uploadToURLParameters) (err error) { + values := url.Values{} + if params.Content != "" { + contentReader := strings.NewReader(params.Content) + err = postWithMultipartResponse(ctx, api.httpclient, params.UploadURL, params.Filename, "file", api.token, values, contentReader, nil, api) + } else if params.File != "" { + err = postLocalWithMultipartResponse(ctx, api.httpclient, params.UploadURL, params.File, "file", api.token, values, nil, api) + } else if params.Reader != nil { + err = postWithMultipartResponse(ctx, api.httpclient, params.UploadURL, params.Filename, "file", api.token, values, params.Reader, nil, api) + } + return err +} + +// completeUploadExternal once files are uploaded, this completes the upload and shares it to the specified channel +func (api *Client) completeUploadExternal(ctx context.Context, fileID string, params completeUploadExternalParameters) (file *completeUploadExternalResponse, err error) { + request := []FileSummary{{ID: fileID, Title: params.title}} + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + values := url.Values{ + "token": {api.token}, + "files": {string(requestBytes)}, + } + + if params.channel != "" { + values.Add("channel_id", params.channel) + } + if params.initialComment != "" { + values.Add("initial_comment", params.initialComment) + } + if params.threadTimestamp != "" { + values.Add("thread_ts", params.threadTimestamp) + } + response := &completeUploadExternalResponse{} + err = api.postMethod(ctx, "files.completeUploadExternal", values, response) + if err != nil { + return nil, err + } + if response.Err() != nil { + return nil, response.Err() + } + return response, nil +} + +// UploadFileV2 uploads file to a given slack channel using 3 steps. +// For more details, see UploadFileV2Context documentation. +func (api *Client) UploadFileV2(params UploadFileV2Parameters) (*FileSummary, error) { + return api.UploadFileV2Context(context.Background(), params) +} + +// UploadFileV2Context uploads file to a given slack channel using 3 steps - +// 1. Get an upload URL using files.getUploadURLExternal API +// 2. Send the file as a post to the URL provided by slack +// 3. Complete the upload and share it to the specified channel using files.completeUploadExternal +// +// Slack Docs: https://api.slack.com/messaging/files#uploading_files +func (api *Client) UploadFileV2Context(ctx context.Context, params UploadFileV2Parameters) (file *FileSummary, err error) { + if params.Filename == "" { + return nil, fmt.Errorf("file.upload.v2: filename cannot be empty") + } + if params.FileSize == 0 { + return nil, fmt.Errorf("file.upload.v2: file size cannot be 0") + } + + u, err := api.getUploadURLExternal(ctx, getUploadURLExternalParameters{ + altText: params.AltTxt, + fileName: params.Filename, + fileSize: params.FileSize, + snippetText: params.SnippetText, + }) + if err != nil { + return nil, err + } + + err = api.uploadToURL(ctx, uploadToURLParameters{ + UploadURL: u.UploadURL, + Reader: params.Reader, + File: params.File, + Content: params.Content, + Filename: params.Filename, + }) + if err != nil { + return nil, err + } + + c, err := api.completeUploadExternal(ctx, u.FileID, completeUploadExternalParameters{ + title: params.Title, + channel: params.Channel, + initialComment: params.InitialComment, + threadTimestamp: params.ThreadTimestamp, + }) + if err != nil { + return nil, err + } + if len(c.Files) != 1 { + return nil, fmt.Errorf("file.upload.v2: something went wrong; received %d files instead of 1", len(c.Files)) + } + + return &c.Files[0], nil +} diff --git a/vendor/github.com/slack-go/slack/groups.go b/vendor/github.com/slack-go/slack/groups.go index 2337486..b77f909 100644 --- a/vendor/github.com/slack-go/slack/groups.go +++ b/vendor/github.com/slack-go/slack/groups.go @@ -1,355 +1,7 @@ package slack -import ( - "context" - "net/url" - "strconv" -) - // Group contains all the information for a group type Group struct { GroupConversation IsGroup bool `json:"is_group"` } - -type groupResponseFull struct { - Group Group `json:"group"` - Groups []Group `json:"groups"` - Purpose string `json:"purpose"` - Topic string `json:"topic"` - NotInGroup bool `json:"not_in_group"` - NoOp bool `json:"no_op"` - AlreadyClosed bool `json:"already_closed"` - AlreadyOpen bool `json:"already_open"` - AlreadyInGroup bool `json:"already_in_group"` - Channel Channel `json:"channel"` - History - SlackResponse -} - -func (api *Client) groupRequest(ctx context.Context, path string, values url.Values) (*groupResponseFull, error) { - response := &groupResponseFull{} - err := api.postMethod(ctx, path, values, response) - if err != nil { - return nil, err - } - - return response, response.Err() -} - -// ArchiveGroup archives a private group -func (api *Client) ArchiveGroup(group string) error { - return api.ArchiveGroupContext(context.Background(), group) -} - -// ArchiveGroupContext archives a private group -func (api *Client) ArchiveGroupContext(ctx context.Context, group string) error { - values := url.Values{ - "token": {api.token}, - "channel": {group}, - } - - _, err := api.groupRequest(ctx, "groups.archive", values) - return err -} - -// UnarchiveGroup unarchives a private group -func (api *Client) UnarchiveGroup(group string) error { - return api.UnarchiveGroupContext(context.Background(), group) -} - -// UnarchiveGroupContext unarchives a private group -func (api *Client) UnarchiveGroupContext(ctx context.Context, group string) error { - values := url.Values{ - "token": {api.token}, - "channel": {group}, - } - - _, err := api.groupRequest(ctx, "groups.unarchive", values) - return err -} - -// CreateGroup creates a private group -func (api *Client) CreateGroup(group string) (*Group, error) { - return api.CreateGroupContext(context.Background(), group) -} - -// CreateGroupContext creates a private group -func (api *Client) CreateGroupContext(ctx context.Context, group string) (*Group, error) { - values := url.Values{ - "token": {api.token}, - "name": {group}, - } - - response, err := api.groupRequest(ctx, "groups.create", values) - if err != nil { - return nil, err - } - return &response.Group, nil -} - -// CreateChildGroup creates a new private group archiving the old one -// This method takes an existing private group and performs the following steps: -// 1. Renames the existing group (from "example" to "example-archived"). -// 2. Archives the existing group. -// 3. Creates a new group with the name of the existing group. -// 4. Adds all members of the existing group to the new group. -func (api *Client) CreateChildGroup(group string) (*Group, error) { - return api.CreateChildGroupContext(context.Background(), group) -} - -// CreateChildGroupContext creates a new private group archiving the old one with a custom context -// For more information see CreateChildGroup -func (api *Client) CreateChildGroupContext(ctx context.Context, group string) (*Group, error) { - values := url.Values{ - "token": {api.token}, - "channel": {group}, - } - - response, err := api.groupRequest(ctx, "groups.createChild", values) - if err != nil { - return nil, err - } - return &response.Group, nil -} - -// GetGroupHistory fetches all the history for a private group -func (api *Client) GetGroupHistory(group string, params HistoryParameters) (*History, error) { - return api.GetGroupHistoryContext(context.Background(), group, params) -} - -// GetGroupHistoryContext fetches all the history for a private group with a custom context -func (api *Client) GetGroupHistoryContext(ctx context.Context, group string, params HistoryParameters) (*History, error) { - values := url.Values{ - "token": {api.token}, - "channel": {group}, - } - if params.Latest != DEFAULT_HISTORY_LATEST { - values.Add("latest", params.Latest) - } - if params.Oldest != DEFAULT_HISTORY_OLDEST { - values.Add("oldest", params.Oldest) - } - if params.Count != DEFAULT_HISTORY_COUNT { - values.Add("count", strconv.Itoa(params.Count)) - } - if params.Inclusive != DEFAULT_HISTORY_INCLUSIVE { - if params.Inclusive { - values.Add("inclusive", "1") - } else { - values.Add("inclusive", "0") - } - } - if params.Unreads != DEFAULT_HISTORY_UNREADS { - if params.Unreads { - values.Add("unreads", "1") - } else { - values.Add("unreads", "0") - } - } - - response, err := api.groupRequest(ctx, "groups.history", values) - if err != nil { - return nil, err - } - return &response.History, nil -} - -// InviteUserToGroup invites a specific user to a private group -func (api *Client) InviteUserToGroup(group, user string) (*Group, bool, error) { - return api.InviteUserToGroupContext(context.Background(), group, user) -} - -// InviteUserToGroupContext invites a specific user to a private group with a custom context -func (api *Client) InviteUserToGroupContext(ctx context.Context, group, user string) (*Group, bool, error) { - values := url.Values{ - "token": {api.token}, - "channel": {group}, - "user": {user}, - } - - response, err := api.groupRequest(ctx, "groups.invite", values) - if err != nil { - return nil, false, err - } - return &response.Group, response.AlreadyInGroup, nil -} - -// LeaveGroup makes authenticated user leave the group -func (api *Client) LeaveGroup(group string) error { - return api.LeaveGroupContext(context.Background(), group) -} - -// LeaveGroupContext makes authenticated user leave the group with a custom context -func (api *Client) LeaveGroupContext(ctx context.Context, group string) (err error) { - values := url.Values{ - "token": {api.token}, - "channel": {group}, - } - - _, err = api.groupRequest(ctx, "groups.leave", values) - return err -} - -// KickUserFromGroup kicks a user from a group -func (api *Client) KickUserFromGroup(group, user string) error { - return api.KickUserFromGroupContext(context.Background(), group, user) -} - -// KickUserFromGroupContext kicks a user from a group with a custom context -func (api *Client) KickUserFromGroupContext(ctx context.Context, group, user string) (err error) { - values := url.Values{ - "token": {api.token}, - "channel": {group}, - "user": {user}, - } - - _, err = api.groupRequest(ctx, "groups.kick", values) - return err -} - -// GetGroups retrieves all groups -func (api *Client) GetGroups(excludeArchived bool) ([]Group, error) { - return api.GetGroupsContext(context.Background(), excludeArchived) -} - -// GetGroupsContext retrieves all groups with a custom context -func (api *Client) GetGroupsContext(ctx context.Context, excludeArchived bool) ([]Group, error) { - values := url.Values{ - "token": {api.token}, - } - if excludeArchived { - values.Add("exclude_archived", "1") - } - - response, err := api.groupRequest(ctx, "groups.list", values) - if err != nil { - return nil, err - } - return response.Groups, nil -} - -// GetGroupInfo retrieves the given group -func (api *Client) GetGroupInfo(group string) (*Group, error) { - return api.GetGroupInfoContext(context.Background(), group) -} - -// GetGroupInfoContext retrieves the given group with a custom context -func (api *Client) GetGroupInfoContext(ctx context.Context, group string) (*Group, error) { - values := url.Values{ - "token": {api.token}, - "channel": {group}, - "include_locale": {strconv.FormatBool(true)}, - } - - response, err := api.groupRequest(ctx, "groups.info", values) - if err != nil { - return nil, err - } - return &response.Group, nil -} - -// SetGroupReadMark sets the read mark on a private group -// Clients should try to avoid making this call too often. When needing to mark a read position, a client should set a -// timer before making the call. In this way, any further updates needed during the timeout will not generate extra -// calls (just one per channel). This is useful for when reading scroll-back history, or following a busy live -// channel. A timeout of 5 seconds is a good starting point. Be sure to flush these calls on shutdown/logout. -func (api *Client) SetGroupReadMark(group, ts string) error { - return api.SetGroupReadMarkContext(context.Background(), group, ts) -} - -// SetGroupReadMarkContext sets the read mark on a private group with a custom context -// For more details see SetGroupReadMark -func (api *Client) SetGroupReadMarkContext(ctx context.Context, group, ts string) (err error) { - values := url.Values{ - "token": {api.token}, - "channel": {group}, - "ts": {ts}, - } - - _, err = api.groupRequest(ctx, "groups.mark", values) - return err -} - -// OpenGroup opens a private group -func (api *Client) OpenGroup(group string) (bool, bool, error) { - return api.OpenGroupContext(context.Background(), group) -} - -// OpenGroupContext opens a private group with a custom context -func (api *Client) OpenGroupContext(ctx context.Context, group string) (bool, bool, error) { - values := url.Values{ - "token": {api.token}, - "channel": {group}, - } - - response, err := api.groupRequest(ctx, "groups.open", values) - if err != nil { - return false, false, err - } - return response.NoOp, response.AlreadyOpen, nil -} - -// RenameGroup renames a group -// XXX: They return a channel, not a group. What is this crap? :( -// Inconsistent api it seems. -func (api *Client) RenameGroup(group, name string) (*Channel, error) { - return api.RenameGroupContext(context.Background(), group, name) -} - -// RenameGroupContext renames a group with a custom context -func (api *Client) RenameGroupContext(ctx context.Context, group, name string) (*Channel, error) { - values := url.Values{ - "token": {api.token}, - "channel": {group}, - "name": {name}, - } - - // XXX: the created entry in this call returns a string instead of a number - // so I may have to do some workaround to solve it. - response, err := api.groupRequest(ctx, "groups.rename", values) - if err != nil { - return nil, err - } - return &response.Channel, nil -} - -// SetGroupPurpose sets the group purpose -func (api *Client) SetGroupPurpose(group, purpose string) (string, error) { - return api.SetGroupPurposeContext(context.Background(), group, purpose) -} - -// SetGroupPurposeContext sets the group purpose with a custom context -func (api *Client) SetGroupPurposeContext(ctx context.Context, group, purpose string) (string, error) { - values := url.Values{ - "token": {api.token}, - "channel": {group}, - "purpose": {purpose}, - } - - response, err := api.groupRequest(ctx, "groups.setPurpose", values) - if err != nil { - return "", err - } - return response.Purpose, nil -} - -// SetGroupTopic sets the group topic -func (api *Client) SetGroupTopic(group, topic string) (string, error) { - return api.SetGroupTopicContext(context.Background(), group, topic) -} - -// SetGroupTopicContext sets the group topic with a custom context -func (api *Client) SetGroupTopicContext(ctx context.Context, group, topic string) (string, error) { - values := url.Values{ - "token": {api.token}, - "channel": {group}, - "topic": {topic}, - } - - response, err := api.groupRequest(ctx, "groups.setTopic", values) - if err != nil { - return "", err - } - return response.Topic, nil -} diff --git a/vendor/github.com/slack-go/slack/history.go b/vendor/github.com/slack-go/slack/history.go index 87b2e1e..49dfe35 100644 --- a/vendor/github.com/slack-go/slack/history.go +++ b/vendor/github.com/slack-go/slack/history.go @@ -22,6 +22,7 @@ type History struct { Latest string `json:"latest"` Messages []Message `json:"messages"` HasMore bool `json:"has_more"` + Unread int `json:"unread_count_display"` } // NewHistoryParameters provides an instance of HistoryParameters with all the sane default values set diff --git a/vendor/github.com/slack-go/slack/im.go b/vendor/github.com/slack-go/slack/im.go index ee784fe..7c4bc25 100644 --- a/vendor/github.com/slack-go/slack/im.go +++ b/vendor/github.com/slack-go/slack/im.go @@ -1,11 +1,5 @@ package slack -import ( - "context" - "net/url" - "strconv" -) - type imChannel struct { ID string `json:"id"` } @@ -25,130 +19,3 @@ type IM struct { Conversation IsUserDeleted bool `json:"is_user_deleted"` } - -func (api *Client) imRequest(ctx context.Context, path string, values url.Values) (*imResponseFull, error) { - response := &imResponseFull{} - err := api.postMethod(ctx, path, values, response) - if err != nil { - return nil, err - } - - return response, response.Err() -} - -// CloseIMChannel closes the direct message channel -func (api *Client) CloseIMChannel(channel string) (bool, bool, error) { - return api.CloseIMChannelContext(context.Background(), channel) -} - -// CloseIMChannelContext closes the direct message channel with a custom context -func (api *Client) CloseIMChannelContext(ctx context.Context, channel string) (bool, bool, error) { - values := url.Values{ - "token": {api.token}, - "channel": {channel}, - } - - response, err := api.imRequest(ctx, "im.close", values) - if err != nil { - return false, false, err - } - return response.NoOp, response.AlreadyClosed, nil -} - -// OpenIMChannel opens a direct message channel to the user provided as argument -// Returns some status and the channel ID -func (api *Client) OpenIMChannel(user string) (bool, bool, string, error) { - return api.OpenIMChannelContext(context.Background(), user) -} - -// OpenIMChannelContext opens a direct message channel to the user provided as argument with a custom context -// Returns some status and the channel ID -func (api *Client) OpenIMChannelContext(ctx context.Context, user string) (bool, bool, string, error) { - values := url.Values{ - "token": {api.token}, - "user": {user}, - } - - response, err := api.imRequest(ctx, "im.open", values) - if err != nil { - return false, false, "", err - } - return response.NoOp, response.AlreadyOpen, response.Channel.ID, nil -} - -// MarkIMChannel sets the read mark of a direct message channel to a specific point -func (api *Client) MarkIMChannel(channel, ts string) (err error) { - return api.MarkIMChannelContext(context.Background(), channel, ts) -} - -// MarkIMChannelContext sets the read mark of a direct message channel to a specific point with a custom context -func (api *Client) MarkIMChannelContext(ctx context.Context, channel, ts string) error { - values := url.Values{ - "token": {api.token}, - "channel": {channel}, - "ts": {ts}, - } - - _, err := api.imRequest(ctx, "im.mark", values) - return err -} - -// GetIMHistory retrieves the direct message channel history -func (api *Client) GetIMHistory(channel string, params HistoryParameters) (*History, error) { - return api.GetIMHistoryContext(context.Background(), channel, params) -} - -// GetIMHistoryContext retrieves the direct message channel history with a custom context -func (api *Client) GetIMHistoryContext(ctx context.Context, channel string, params HistoryParameters) (*History, error) { - values := url.Values{ - "token": {api.token}, - "channel": {channel}, - } - if params.Latest != DEFAULT_HISTORY_LATEST { - values.Add("latest", params.Latest) - } - if params.Oldest != DEFAULT_HISTORY_OLDEST { - values.Add("oldest", params.Oldest) - } - if params.Count != DEFAULT_HISTORY_COUNT { - values.Add("count", strconv.Itoa(params.Count)) - } - if params.Inclusive != DEFAULT_HISTORY_INCLUSIVE { - if params.Inclusive { - values.Add("inclusive", "1") - } else { - values.Add("inclusive", "0") - } - } - if params.Unreads != DEFAULT_HISTORY_UNREADS { - if params.Unreads { - values.Add("unreads", "1") - } else { - values.Add("unreads", "0") - } - } - - response, err := api.imRequest(ctx, "im.history", values) - if err != nil { - return nil, err - } - return &response.History, nil -} - -// GetIMChannels returns the list of direct message channels -func (api *Client) GetIMChannels() ([]IM, error) { - return api.GetIMChannelsContext(context.Background()) -} - -// GetIMChannelsContext returns the list of direct message channels with a custom context -func (api *Client) GetIMChannelsContext(ctx context.Context) ([]IM, error) { - values := url.Values{ - "token": {api.token}, - } - - response, err := api.imRequest(ctx, "im.list", values) - if err != nil { - return nil, err - } - return response.IMs, nil -} diff --git a/vendor/github.com/slack-go/slack/info.go b/vendor/github.com/slack-go/slack/info.go index ec70624..b06dffd 100644 --- a/vendor/github.com/slack-go/slack/info.go +++ b/vendor/github.com/slack-go/slack/info.go @@ -321,10 +321,13 @@ type UserPrefs struct { } func (api *Client) GetUserPrefs() (*UserPrefsCarrier, error) { - values := url.Values{"token": {api.token}} + return api.GetUserPrefsContext(context.Background()) +} + +func (api *Client) GetUserPrefsContext(ctx context.Context) (*UserPrefsCarrier, error) { response := UserPrefsCarrier{} - err := api.getMethod(context.Background(), "users.prefs.get", values, &response) + err := api.getMethod(ctx, "users.prefs.get", api.token, url.Values{}, &response) if err != nil { return nil, err } @@ -406,6 +409,11 @@ func (t JSONTime) Time() time.Time { func (t *JSONTime) UnmarshalJSON(buf []byte) error { s := bytes.Trim(buf, `"`) + if bytes.EqualFold(s, []byte("null")) { + *t = JSONTime(0) + return nil + } + v, err := strconv.Atoi(string(s)) if err != nil { return err diff --git a/vendor/github.com/slack-go/slack/interactions.go b/vendor/github.com/slack-go/slack/interactions.go index c56a34f..8c64147 100644 --- a/vendor/github.com/slack-go/slack/interactions.go +++ b/vendor/github.com/slack-go/slack/interactions.go @@ -9,11 +9,11 @@ import ( type InteractionType string // ActionType type represents the type of action (attachment, block, etc.) -type actionType string +type ActionType string // action is an interface that should be implemented by all callback action types type action interface { - actionType() actionType + actionType() ActionType } // Types of interactions that can be received. @@ -27,39 +27,122 @@ const ( InteractionTypeBlockSuggestion = InteractionType("block_suggestion") InteractionTypeViewSubmission = InteractionType("view_submission") InteractionTypeViewClosed = InteractionType("view_closed") + InteractionTypeShortcut = InteractionType("shortcut") + InteractionTypeWorkflowStepEdit = InteractionType("workflow_step_edit") ) // InteractionCallback is sent from slack when a user interactions with a button or dialog. type InteractionCallback struct { - Type InteractionType `json:"type"` - Token string `json:"token"` - CallbackID string `json:"callback_id"` - ResponseURL string `json:"response_url"` - TriggerID string `json:"trigger_id"` - ActionTs string `json:"action_ts"` - Team Team `json:"team"` - Channel Channel `json:"channel"` - User User `json:"user"` - OriginalMessage Message `json:"original_message"` - Message Message `json:"message"` - Name string `json:"name"` - Value string `json:"value"` - MessageTs string `json:"message_ts"` - AttachmentID string `json:"attachment_id"` - ActionCallback ActionCallbacks `json:"actions"` - View View `json:"view"` - ActionID string `json:"action_id"` - APIAppID string `json:"api_app_id"` - BlockID string `json:"block_id"` - Container Container `json:"container"` + Type InteractionType `json:"type"` + Token string `json:"token"` + CallbackID string `json:"callback_id"` + ResponseURL string `json:"response_url"` + TriggerID string `json:"trigger_id"` + ActionTs string `json:"action_ts"` + Team Team `json:"team"` + Channel Channel `json:"channel"` + User User `json:"user"` + OriginalMessage Message `json:"original_message"` + Message Message `json:"message"` + Name string `json:"name"` + Value string `json:"value"` + MessageTs string `json:"message_ts"` + AttachmentID string `json:"attachment_id"` + ActionCallback ActionCallbacks `json:"actions"` + View View `json:"view"` + ActionID string `json:"action_id"` + APIAppID string `json:"api_app_id"` + BlockID string `json:"block_id"` + Container Container `json:"container"` + Enterprise Enterprise `json:"enterprise"` + IsEnterpriseInstall bool `json:"is_enterprise_install"` + WorkflowStep InteractionWorkflowStep `json:"workflow_step"` DialogSubmissionCallback ViewSubmissionCallback ViewClosedCallback + + // FIXME(kanata2): just workaround for backward-compatibility. + // See also https://github.com/slack-go/slack/issues/816 + RawState json.RawMessage `json:"state,omitempty"` + + // BlockActionState stands for the `state` field in block_actions type. + // NOTE: InteractionCallback.State has a role for the state of dialog_submission type, + // so we cannot use this field for backward-compatibility for now. + BlockActionState *BlockActionStates `json:"-"` +} + +type BlockActionStates struct { + Values map[string]map[string]BlockAction `json:"values"` +} + +func (ic *InteractionCallback) MarshalJSON() ([]byte, error) { + type alias InteractionCallback + tmp := alias(*ic) + if tmp.Type == InteractionTypeBlockActions { + if tmp.BlockActionState == nil { + tmp.RawState = []byte(`{}`) + } else { + state, err := json.Marshal(tmp.BlockActionState.Values) + if err != nil { + return nil, err + } + tmp.RawState = []byte(`{"values":` + string(state) + `}`) + } + } else if ic.Type == InteractionTypeDialogSubmission { + tmp.RawState = []byte(tmp.State) + } + // Use pointer for go1.7 + return json.Marshal(&tmp) +} + +func (ic *InteractionCallback) UnmarshalJSON(b []byte) error { + type alias InteractionCallback + tmp := struct { + Type InteractionType `json:"type"` + *alias + }{ + alias: (*alias)(ic), + } + if err := json.Unmarshal(b, &tmp); err != nil { + return err + } + *ic = InteractionCallback(*tmp.alias) + ic.Type = tmp.Type + if ic.Type == InteractionTypeBlockActions { + if len(ic.RawState) > 0 { + err := json.Unmarshal(ic.RawState, &ic.BlockActionState) + if err != nil { + return err + } + } + } else if ic.Type == InteractionTypeDialogSubmission { + ic.State = string(ic.RawState) + } + return nil } type Container struct { - Type string `json:"type"` - ViewID string `json:"view_id"` + Type string `json:"type"` + ViewID string `json:"view_id"` + MessageTs string `json:"message_ts"` + ThreadTs string `json:"thread_ts,omitempty"` + AttachmentID json.Number `json:"attachment_id"` + ChannelID string `json:"channel_id"` + IsEphemeral bool `json:"is_ephemeral"` + IsAppUnfurl bool `json:"is_app_unfurl"` +} + +type Enterprise struct { + ID string `json:"id"` + Name string `json:"name"` +} + +type InteractionWorkflowStep struct { + WorkflowStepEditID string `json:"workflow_step_edit_id,omitempty"` + WorkflowID string `json:"workflow_id"` + StepID string `json:"step_id"` + Inputs *WorkflowStepInputs `json:"inputs,omitempty"` + Outputs *[]WorkflowStepOutput `json:"outputs,omitempty"` } // ActionCallback is a convenience struct defined to allow dynamic unmarshalling of @@ -134,7 +217,7 @@ func (a *ActionCallbacks) UnmarshalJSON(data []byte) error { } a.BlockActions = append(a.BlockActions, action.(*BlockAction)) - return nil + continue } action, err := unmarshalAction(r, &AttachmentAction{}) diff --git a/vendor/github.com/slack-go/slack/backoff.go b/vendor/github.com/slack-go/slack/internal/backoff/backoff.go similarity index 79% rename from vendor/github.com/slack-go/slack/backoff.go rename to vendor/github.com/slack-go/slack/internal/backoff/backoff.go index 2ba697e..833e9f2 100644 --- a/vendor/github.com/slack-go/slack/backoff.go +++ b/vendor/github.com/slack-go/slack/internal/backoff/backoff.go @@ -1,4 +1,4 @@ -package slack +package backoff import ( "math/rand" @@ -11,7 +11,7 @@ import ( // call to Duration() it is multiplied by Factor. It is capped at // Max. It returns to Min on every call to Reset(). Used in // conjunction with the time package. -type backoff struct { +type Backoff struct { attempts int // Initial value to scale out Initial time.Duration @@ -23,7 +23,7 @@ type backoff struct { // Returns the current value of the counter and then multiplies it // Factor -func (b *backoff) Duration() (dur time.Duration) { +func (b *Backoff) Duration() (dur time.Duration) { // Zero-values are nonsensical, so we use // them to apply defaults if b.Max == 0 { @@ -51,7 +51,12 @@ func (b *backoff) Duration() (dur time.Duration) { return dur } -//Resets the current value of the counter back to Min -func (b *backoff) Reset() { +// Resets the current value of the counter back to Min +func (b *Backoff) Reset() { b.attempts = 0 } + +// Attempts returns the number of attempts that we had done so far +func (b *Backoff) Attempts() int { + return b.attempts +} diff --git a/vendor/github.com/slack-go/slack/internal/errorsx/errorsx.go b/vendor/github.com/slack-go/slack/internal/errorsx/errorsx.go index cb85057..0182ec6 100644 --- a/vendor/github.com/slack-go/slack/internal/errorsx/errorsx.go +++ b/vendor/github.com/slack-go/slack/internal/errorsx/errorsx.go @@ -6,3 +6,12 @@ type String string func (t String) Error() string { return string(t) } + +// Is reports whether String matches with the target error +func (t String) Is(target error) bool { + if target == nil { + return false + } + + return t.Error() == target.Error() +} diff --git a/vendor/github.com/slack-go/slack/logger.go b/vendor/github.com/slack-go/slack/logger.go index 6a3533a..90cb3ca 100644 --- a/vendor/github.com/slack-go/slack/logger.go +++ b/vendor/github.com/slack-go/slack/logger.go @@ -18,7 +18,7 @@ type ilogger interface { Println(...interface{}) } -type debug interface { +type Debug interface { Debug() bool // Debugf print a formatted debug line. diff --git a/vendor/github.com/slack-go/slack/logo.png b/vendor/github.com/slack-go/slack/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9bd143459bfe55bb6e01e721623c51a367be7190 GIT binary patch literal 52440 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

G+=^K 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 500 || t.Code == http.StatusTooManyRequests { - return true - } - return false -} - -// RateLimitedError represents the rate limit respond from slack +// RateLimitedError represents the rate limit response from slack type RateLimitedError struct { RetryAfter time.Duration } @@ -76,30 +62,27 @@ func (e *RateLimitedError) Retryable() bool { return true } -func fileUploadReq(ctx context.Context, path string, values url.Values, r io.Reader) (*http.Request, error) { - req, err := http.NewRequest("POST", path, r) +func fileUploadReq(ctx context.Context, path string, r io.Reader) (*http.Request, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodPost, path, r) if err != nil { return nil, err } - req = req.WithContext(ctx) - req.URL.RawQuery = (values).Encode() return req, nil } -func downloadFile(client httpClient, token string, downloadURL string, writer io.Writer, d debug) error { +func downloadFile(ctx context.Context, client httpClient, token string, downloadURL string, writer io.Writer, d Debug) error { if downloadURL == "" { return fmt.Errorf("received empty download URL") } - req, err := http.NewRequest("GET", downloadURL, &bytes.Buffer{}) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadURL, &bytes.Buffer{}) if err != nil { return err } var bearer = "Bearer " + token req.Header.Add("Authorization", bearer) - req.WithContext(context.Background()) resp, err := client.Do(req) if err != nil { @@ -118,8 +101,8 @@ func downloadFile(client httpClient, token string, downloadURL string, writer io return err } -func formReq(endpoint string, values url.Values) (req *http.Request, err error) { - if req, err = http.NewRequest("POST", endpoint, strings.NewReader(values.Encode())); err != nil { +func formReq(ctx context.Context, endpoint string, values url.Values) (req *http.Request, err error) { + if req, err = http.NewRequestWithContext(ctx, http.MethodPost, endpoint, strings.NewReader(values.Encode())); err != nil { return nil, err } @@ -127,13 +110,13 @@ func formReq(endpoint string, values url.Values) (req *http.Request, err error) return req, nil } -func jsonReq(endpoint string, body interface{}) (req *http.Request, err error) { +func jsonReq(ctx context.Context, endpoint string, body interface{}) (req *http.Request, err error) { buffer := bytes.NewBuffer([]byte{}) if err = json.NewEncoder(buffer).Encode(body); err != nil { return nil, err } - if req, err = http.NewRequest("POST", endpoint, buffer); err != nil { + if req, err = http.NewRequestWithContext(ctx, http.MethodPost, endpoint, buffer); err != nil { return nil, err } @@ -141,8 +124,8 @@ func jsonReq(endpoint string, body interface{}) (req *http.Request, err error) { return req, nil } -func parseResponseBody(body io.ReadCloser, intf interface{}, d debug) error { - response, err := ioutil.ReadAll(body) +func parseResponseBody(body io.ReadCloser, intf interface{}, d Debug) error { + response, err := io.ReadAll(body) if err != nil { return err } @@ -154,7 +137,7 @@ func parseResponseBody(body io.ReadCloser, intf interface{}, d debug) error { return json.Unmarshal(response, intf) } -func postLocalWithMultipartResponse(ctx context.Context, client httpClient, method, fpath, fieldname string, values url.Values, intf interface{}, d debug) error { +func postLocalWithMultipartResponse(ctx context.Context, client httpClient, method, fpath, fieldname, token string, values url.Values, intf interface{}, d Debug) error { fullpath, err := filepath.Abs(fpath) if err != nil { return err @@ -165,15 +148,22 @@ func postLocalWithMultipartResponse(ctx context.Context, client httpClient, meth } defer file.Close() - return postWithMultipartResponse(ctx, client, method, filepath.Base(fpath), fieldname, values, file, intf, d) + return postWithMultipartResponse(ctx, client, method, filepath.Base(fpath), fieldname, token, values, file, intf, d) } -func postWithMultipartResponse(ctx context.Context, client httpClient, path, name, fieldname string, values url.Values, r io.Reader, intf interface{}, d debug) error { +func postWithMultipartResponse(ctx context.Context, client httpClient, path, name, fieldname, token string, values url.Values, r io.Reader, intf interface{}, d Debug) error { pipeReader, pipeWriter := io.Pipe() wr := multipart.NewWriter(pipeWriter) + errc := make(chan error) go func() { defer pipeWriter.Close() + defer wr.Close() + err := createFormFields(wr, values) + if err != nil { + errc <- err + return + } ioWriter, err := wr.CreateFormFile(fieldname, name) if err != nil { errc <- err @@ -189,12 +179,13 @@ func postWithMultipartResponse(ctx context.Context, client httpClient, path, nam return } }() - req, err := fileUploadReq(ctx, path, values, pipeReader) + + req, err := fileUploadReq(ctx, path, pipeReader) if err != nil { return err } req.Header.Add("Content-Type", wr.FormDataContentType()) - req = req.WithContext(ctx) + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) resp, err := client.Do(req) if err != nil { @@ -215,8 +206,21 @@ func postWithMultipartResponse(ctx context.Context, client httpClient, path, nam } } -func doPost(ctx context.Context, client httpClient, req *http.Request, parser responseParser, d debug) error { - req = req.WithContext(ctx) +func createFormFields(mw *multipart.Writer, values url.Values) error { + for key, value := range values { + writer, err := mw.CreateFormField(key) + if err != nil { + return err + } + _, err = writer.Write([]byte(value[0])) + if err != nil { + return err + } + } + return nil +} + +func doPost(ctx context.Context, client httpClient, req *http.Request, parser responseParser, d Debug) error { resp, err := client.Do(req) if err != nil { return err @@ -232,9 +236,9 @@ func doPost(ctx context.Context, client httpClient, req *http.Request, parser re } // post JSON. -func postJSON(ctx context.Context, client httpClient, endpoint, token string, json []byte, intf interface{}, d debug) error { +func postJSON(ctx context.Context, client httpClient, endpoint, token string, json []byte, intf interface{}, d Debug) error { reqBody := bytes.NewBuffer(json) - req, err := http.NewRequest("POST", endpoint, reqBody) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, reqBody) if err != nil { return err } @@ -245,9 +249,9 @@ func postJSON(ctx context.Context, client httpClient, endpoint, token string, js } // post a url encoded form. -func postForm(ctx context.Context, client httpClient, endpoint string, values url.Values, intf interface{}, d debug) error { +func postForm(ctx context.Context, client httpClient, endpoint string, values url.Values, intf interface{}, d Debug) error { reqBody := strings.NewReader(values.Encode()) - req, err := http.NewRequest("POST", endpoint, reqBody) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, reqBody) if err != nil { return err } @@ -255,23 +259,25 @@ func postForm(ctx context.Context, client httpClient, endpoint string, values ur return doPost(ctx, client, req, newJSONParser(intf), d) } -func getResource(ctx context.Context, client httpClient, endpoint string, values url.Values, intf interface{}, d debug) error { - req, err := http.NewRequest("GET", endpoint, nil) +func getResource(ctx context.Context, client httpClient, endpoint, token string, values url.Values, intf interface{}, d Debug) error { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { return err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + req.URL.RawQuery = values.Encode() return doPost(ctx, client, req, newJSONParser(intf), d) } -func parseAdminResponse(ctx context.Context, client httpClient, method string, teamName string, values url.Values, intf interface{}, d debug) error { +func parseAdminResponse(ctx context.Context, client httpClient, method string, teamName string, values url.Values, intf interface{}, d Debug) error { endpoint := fmt.Sprintf(WEBAPIURLFormat, teamName, method, time.Now().Unix()) return postForm(ctx, client, endpoint, values, intf, d) } -func logResponse(resp *http.Response, d debug) error { +func logResponse(resp *http.Response, d Debug) error { if d.Debug() { text, err := httputil.DumpResponse(resp, true) if err != nil { @@ -299,7 +305,7 @@ func timerReset(t *time.Timer, d time.Duration) { t.Reset(d) } -func checkStatusCode(resp *http.Response, d debug) error { +func checkStatusCode(resp *http.Response, d Debug) error { if resp.StatusCode == http.StatusTooManyRequests { retry, err := strconv.ParseInt(resp.Header.Get("Retry-After"), 10, 64) if err != nil { @@ -311,7 +317,7 @@ func checkStatusCode(resp *http.Response, d debug) error { // Slack seems to send an HTML body along with 5xx error codes. Don't parse it. if resp.StatusCode != http.StatusOK { logResponse(resp, d) - return statusCodeError{Code: resp.StatusCode, Status: resp.Status} + return StatusCodeError{Code: resp.StatusCode, Status: resp.Status} } return nil @@ -321,13 +327,16 @@ type responseParser func(*http.Response) error func newJSONParser(dst interface{}) responseParser { return func(resp *http.Response) error { + if dst == nil { + return nil + } return json.NewDecoder(resp.Body).Decode(dst) } } func newTextParser(dst interface{}) responseParser { return func(resp *http.Response) error { - b, err := ioutil.ReadAll(resp.Body) + b, err := io.ReadAll(resp.Body) if err != nil { return err } diff --git a/vendor/github.com/slack-go/slack/oauth.go b/vendor/github.com/slack-go/slack/oauth.go index 64118fb..0c77eca 100644 --- a/vendor/github.com/slack-go/slack/oauth.go +++ b/vendor/github.com/slack-go/slack/oauth.go @@ -33,15 +33,18 @@ type OAuthResponse struct { // OAuthV2Response ... type OAuthV2Response struct { - AccessToken string `json:"access_token"` - TokenType string `json:"token_type"` - Scope string `json:"scope"` - BotUserID string `json:"bot_user_id"` - AppID string `json:"app_id"` - TeamID string `json:"team_id"` - Team OAuthV2ResponseTeam `json:"team"` - Enterprise OAuthV2ResponseEnterprise `json:"enterprise"` - AuthedUser OAuthV2ResponseAuthedUser `json:"authed_user"` + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + Scope string `json:"scope"` + BotUserID string `json:"bot_user_id"` + AppID string `json:"app_id"` + Team OAuthV2ResponseTeam `json:"team"` + IncomingWebhook OAuthResponseIncomingWebhook `json:"incoming_webhook"` + Enterprise OAuthV2ResponseEnterprise `json:"enterprise"` + IsEnterpriseInstall bool `json:"is_enterprise_install"` + AuthedUser OAuthV2ResponseAuthedUser `json:"authed_user"` + RefreshToken string `json:"refresh_token"` + ExpiresIn int `json:"expires_in"` SlackResponse } @@ -59,18 +62,31 @@ type OAuthV2ResponseEnterprise struct { // OAuthV2ResponseAuthedUser ... type OAuthV2ResponseAuthedUser struct { - ID string `json:"id"` - Scope string `json:"scope"` + ID string `json:"id"` + Scope string `json:"scope"` + AccessToken string `json:"access_token"` + ExpiresIn int `json:"expires_in"` + RefreshToken string `json:"refresh_token"` + TokenType string `json:"token_type"` +} + +// OpenIDConnectResponse ... +type OpenIDConnectResponse struct { + Ok bool `json:"ok"` AccessToken string `json:"access_token"` TokenType string `json:"token_type"` + IdToken string `json:"id_token"` + SlackResponse } -// GetOAuthToken retrieves an AccessToken +// GetOAuthToken retrieves an AccessToken. +// For more details, see GetOAuthTokenContext documentation. func GetOAuthToken(client httpClient, clientID, clientSecret, code, redirectURI string) (accessToken string, scope string, err error) { return GetOAuthTokenContext(context.Background(), client, clientID, clientSecret, code, redirectURI) } -// GetOAuthTokenContext retrieves an AccessToken with a custom context +// GetOAuthTokenContext retrieves an AccessToken with a custom context. +// For more details, see GetOAuthResponseContext documentation. func GetOAuthTokenContext(ctx context.Context, client httpClient, clientID, clientSecret, code, redirectURI string) (accessToken string, scope string, err error) { response, err := GetOAuthResponseContext(ctx, client, clientID, clientSecret, code, redirectURI) if err != nil { @@ -79,10 +95,30 @@ func GetOAuthTokenContext(ctx context.Context, client httpClient, clientID, clie return response.AccessToken, response.Scope, nil } +// GetBotOAuthToken retrieves top-level and bot AccessToken - https://api.slack.com/legacy/oauth#bot_user_access_tokens +// For more details, see GetBotOAuthTokenContext documentation. +func GetBotOAuthToken(client httpClient, clientID, clientSecret, code, redirectURI string) (accessToken string, scope string, bot OAuthResponseBot, err error) { + return GetBotOAuthTokenContext(context.Background(), client, clientID, clientSecret, code, redirectURI) +} + +// GetBotOAuthTokenContext retrieves top-level and bot AccessToken with a custom context. +// For more details, see GetOAuthResponseContext documentation. +func GetBotOAuthTokenContext(ctx context.Context, client httpClient, clientID, clientSecret, code, redirectURI string) (accessToken string, scope string, bot OAuthResponseBot, err error) { + response, err := GetOAuthResponseContext(ctx, client, clientID, clientSecret, code, redirectURI) + if err != nil { + return "", "", OAuthResponseBot{}, err + } + return response.AccessToken, response.Scope, response.Bot, nil +} + +// GetOAuthResponse retrieves OAuth response. +// For more details, see GetOAuthResponseContext documentation. func GetOAuthResponse(client httpClient, clientID, clientSecret, code, redirectURI string) (resp *OAuthResponse, err error) { return GetOAuthResponseContext(context.Background(), client, clientID, clientSecret, code, redirectURI) } +// GetOAuthResponseContext retrieves OAuth response with custom context. +// Slack API docs: https://api.slack.com/methods/oauth.access func GetOAuthResponseContext(ctx context.Context, client httpClient, clientID, clientSecret, code, redirectURI string) (resp *OAuthResponse, err error) { values := url.Values{ "client_id": {clientID}, @@ -97,12 +133,14 @@ func GetOAuthResponseContext(ctx context.Context, client httpClient, clientID, c return response, response.Err() } -// GetOAuthV2Response gets a V2 OAuth access token response - https://api.slack.com/methods/oauth.v2.access +// GetOAuthV2Response gets a V2 OAuth access token response. +// For more details, see GetOAuthV2ResponseContext documentation. func GetOAuthV2Response(client httpClient, clientID, clientSecret, code, redirectURI string) (resp *OAuthV2Response, err error) { return GetOAuthV2ResponseContext(context.Background(), client, clientID, clientSecret, code, redirectURI) } -// GetOAuthV2ResponseContext with a context, gets a V2 OAuth access token response +// GetOAuthV2ResponseContext with a context, gets a V2 OAuth access token response. +// Slack API docs: https://api.slack.com/methods/oauth.v2.access func GetOAuthV2ResponseContext(ctx context.Context, client httpClient, clientID, clientSecret, code, redirectURI string) (resp *OAuthV2Response, err error) { values := url.Values{ "client_id": {clientID}, @@ -116,3 +154,47 @@ func GetOAuthV2ResponseContext(ctx context.Context, client httpClient, clientID, } return response, response.Err() } + +// RefreshOAuthV2Token with a context, gets a V2 OAuth access token response. +// For more details, see RefreshOAuthV2TokenContext documentation. +func RefreshOAuthV2Token(client httpClient, clientID, clientSecret, refreshToken string) (resp *OAuthV2Response, err error) { + return RefreshOAuthV2TokenContext(context.Background(), client, clientID, clientSecret, refreshToken) +} + +// RefreshOAuthV2TokenContext with a context, gets a V2 OAuth access token response. +// Slack API docs: https://api.slack.com/methods/oauth.v2.access +func RefreshOAuthV2TokenContext(ctx context.Context, client httpClient, clientID, clientSecret, refreshToken string) (resp *OAuthV2Response, err error) { + values := url.Values{ + "client_id": {clientID}, + "client_secret": {clientSecret}, + "refresh_token": {refreshToken}, + "grant_type": {"refresh_token"}, + } + response := &OAuthV2Response{} + if err = postForm(ctx, client, APIURL+"oauth.v2.access", values, response, discard{}); err != nil { + return nil, err + } + return response, response.Err() +} + +// GetOpenIDConnectToken exchanges a temporary OAuth verifier code for an access token for Sign in with Slack. +// For more details, see GetOpenIDConnectTokenContext documentation. +func GetOpenIDConnectToken(client httpClient, clientID, clientSecret, code, redirectURI string) (resp *OpenIDConnectResponse, err error) { + return GetOpenIDConnectTokenContext(context.Background(), client, clientID, clientSecret, code, redirectURI) +} + +// GetOpenIDConnectTokenContext with a context, gets an access token for Sign in with Slack. +// Slack API docs: https://api.slack.com/methods/openid.connect.token +func GetOpenIDConnectTokenContext(ctx context.Context, client httpClient, clientID, clientSecret, code, redirectURI string) (resp *OpenIDConnectResponse, err error) { + values := url.Values{ + "client_id": {clientID}, + "client_secret": {clientSecret}, + "code": {code}, + "redirect_uri": {redirectURI}, + } + response := &OpenIDConnectResponse{} + if err = postForm(ctx, client, APIURL+"openid.connect.token", values, response, discard{}); err != nil { + return nil, err + } + return response, response.Err() +} diff --git a/vendor/github.com/slack-go/slack/pins.go b/vendor/github.com/slack-go/slack/pins.go index ef97c8d..5e6cf0c 100644 --- a/vendor/github.com/slack-go/slack/pins.go +++ b/vendor/github.com/slack-go/slack/pins.go @@ -12,12 +12,14 @@ type listPinsResponseFull struct { SlackResponse } -// AddPin pins an item in a channel +// AddPin pins an item in a channel. +// For more details, see AddPinContext documentation. func (api *Client) AddPin(channel string, item ItemRef) error { return api.AddPinContext(context.Background(), channel, item) } -// AddPinContext pins an item in a channel with a custom context +// AddPinContext pins an item in a channel with a custom context. +// Slack API docs: https://api.slack.com/methods/pins.add func (api *Client) AddPinContext(ctx context.Context, channel string, item ItemRef) error { values := url.Values{ "channel": {channel}, @@ -41,12 +43,14 @@ func (api *Client) AddPinContext(ctx context.Context, channel string, item ItemR return response.Err() } -// RemovePin un-pins an item from a channel +// RemovePin un-pins an item from a channel. +// For more details, see RemovePinContext documentation. func (api *Client) RemovePin(channel string, item ItemRef) error { return api.RemovePinContext(context.Background(), channel, item) } -// RemovePinContext un-pins an item from a channel with a custom context +// RemovePinContext un-pins an item from a channel with a custom context. +// Slack API docs: https://api.slack.com/methods/pins.remove func (api *Client) RemovePinContext(ctx context.Context, channel string, item ItemRef) error { values := url.Values{ "channel": {channel}, @@ -71,11 +75,13 @@ func (api *Client) RemovePinContext(ctx context.Context, channel string, item It } // ListPins returns information about the items a user reacted to. +// For more details, see ListPinsContext documentation. func (api *Client) ListPins(channel string) ([]Item, *Paging, error) { return api.ListPinsContext(context.Background(), channel) } // ListPinsContext returns information about the items a user reacted to with a custom context. +// Slack API docs: https://api.slack.com/methods/pins.list func (api *Client) ListPinsContext(ctx context.Context, channel string) ([]Item, *Paging, error) { values := url.Values{ "channel": {channel}, diff --git a/vendor/github.com/slack-go/slack/reactions.go b/vendor/github.com/slack-go/slack/reactions.go index 2a9bd42..240f5ba 100644 --- a/vendor/github.com/slack-go/slack/reactions.go +++ b/vendor/github.com/slack-go/slack/reactions.go @@ -67,10 +67,11 @@ const ( // ListReactionsParameters is the inputs to find all reactions by a user. type ListReactionsParameters struct { - User string - Count int - Page int - Full bool + User string + TeamID string + Count int + Page int + Full bool } // NewListReactionsParameters initializes the inputs to find all reactions @@ -128,11 +129,13 @@ func (res listReactionsResponseFull) extractReactedItems() []ReactedItem { } // AddReaction adds a reaction emoji to a message, file or file comment. +// For more details, see AddReactionContext documentation. func (api *Client) AddReaction(name string, item ItemRef) error { return api.AddReactionContext(context.Background(), name, item) } // AddReactionContext adds a reaction emoji to a message, file or file comment with a custom context. +// Slack API docs: https://api.slack.com/methods/reactions.add func (api *Client) AddReactionContext(ctx context.Context, name string, item ItemRef) error { values := url.Values{ "token": {api.token}, @@ -162,11 +165,13 @@ func (api *Client) AddReactionContext(ctx context.Context, name string, item Ite } // RemoveReaction removes a reaction emoji from a message, file or file comment. +// For more details, see RemoveReactionContext documentation. func (api *Client) RemoveReaction(name string, item ItemRef) error { return api.RemoveReactionContext(context.Background(), name, item) } // RemoveReactionContext removes a reaction emoji from a message, file or file comment with a custom context. +// Slack API docs: https://api.slack.com/methods/reactions.remove func (api *Client) RemoveReactionContext(ctx context.Context, name string, item ItemRef) error { values := url.Values{ "token": {api.token}, @@ -196,11 +201,13 @@ func (api *Client) RemoveReactionContext(ctx context.Context, name string, item } // GetReactions returns details about the reactions on an item. +// For more details, see GetReactionsContext documentation. func (api *Client) GetReactions(item ItemRef, params GetReactionsParameters) ([]ItemReaction, error) { return api.GetReactionsContext(context.Background(), item, params) } -// GetReactionsContext returns details about the reactions on an item with a custom context +// GetReactionsContext returns details about the reactions on an item with a custom context. +// Slack API docs: https://api.slack.com/methods/reactions.get func (api *Client) GetReactionsContext(ctx context.Context, item ItemRef, params GetReactionsParameters) ([]ItemReaction, error) { values := url.Values{ "token": {api.token}, @@ -234,11 +241,13 @@ func (api *Client) GetReactionsContext(ctx context.Context, item ItemRef, params } // ListReactions returns information about the items a user reacted to. +// For more details, see ListReactionsContext documentation. func (api *Client) ListReactions(params ListReactionsParameters) ([]ReactedItem, *Paging, error) { return api.ListReactionsContext(context.Background(), params) } // ListReactionsContext returns information about the items a user reacted to with a custom context. +// Slack API docs: https://api.slack.com/methods/reactions.list func (api *Client) ListReactionsContext(ctx context.Context, params ListReactionsParameters) ([]ReactedItem, *Paging, error) { values := url.Values{ "token": {api.token}, @@ -246,6 +255,9 @@ func (api *Client) ListReactionsContext(ctx context.Context, params ListReaction if params.User != DEFAULT_REACTIONS_USER { values.Add("user", params.User) } + if params.TeamID != "" { + values.Add("team_id", params.TeamID) + } if params.Count != DEFAULT_REACTIONS_COUNT { values.Add("count", strconv.Itoa(params.Count)) } diff --git a/vendor/github.com/slack-go/slack/reminders.go b/vendor/github.com/slack-go/slack/reminders.go index 9b90538..e025bc9 100644 --- a/vendor/github.com/slack-go/slack/reminders.go +++ b/vendor/github.com/slack-go/slack/reminders.go @@ -3,17 +3,16 @@ package slack import ( "context" "net/url" - "time" ) type Reminder struct { - ID string `json:"id"` - Creator string `json:"creator"` - User string `json:"user"` - Text string `json:"text"` - Recurring bool `json:"recurring"` - Time time.Time `json:"time"` - CompleteTS int `json:"complete_ts"` + ID string `json:"id"` + Creator string `json:"creator"` + User string `json:"user"` + Text string `json:"text"` + Recurring bool `json:"recurring"` + Time int `json:"time"` + CompleteTS int `json:"complete_ts"` } type reminderResp struct { @@ -21,6 +20,11 @@ type reminderResp struct { Reminder Reminder `json:"reminder"` } +type remindersResp struct { + SlackResponse + Reminders []*Reminder `json:"reminders"` +} + func (api *Client) doReminder(ctx context.Context, path string, values url.Values) (*Reminder, error) { response := &reminderResp{} if err := api.postMethod(ctx, path, values, response); err != nil { @@ -29,46 +33,85 @@ func (api *Client) doReminder(ctx context.Context, path string, values url.Value return &response.Reminder, response.Err() } +func (api *Client) doReminders(ctx context.Context, path string, values url.Values) ([]*Reminder, error) { + response := &remindersResp{} + if err := api.postMethod(ctx, path, values, response); err != nil { + return nil, err + } + + // create an array of pointers to reminders + var reminders = make([]*Reminder, 0, len(response.Reminders)) + reminders = append(reminders, response.Reminders...) + return reminders, response.Err() +} + +// ListReminders lists all the reminders created by or for the authenticated user +// For more details, see ListRemindersContext documentation. +func (api *Client) ListReminders() ([]*Reminder, error) { + return api.ListRemindersContext(context.Background()) +} + +// ListRemindersContext lists all the reminders created by or for the authenticated user with a custom context. +// Slack API docs: https://api.slack.com/methods/reminders.list +func (api *Client) ListRemindersContext(ctx context.Context) ([]*Reminder, error) { + values := url.Values{ + "token": {api.token}, + } + return api.doReminders(ctx, "reminders.list", values) +} + // AddChannelReminder adds a reminder for a channel. -// -// See https://api.slack.com/methods/reminders.add (NOTE: the ability to set -// reminders on a channel is currently undocumented but has been tested to -// work) +// For more details, see AddChannelReminderContext documentation. func (api *Client) AddChannelReminder(channelID, text, time string) (*Reminder, error) { + return api.AddChannelReminderContext(context.Background(), channelID, text, time) +} + +// AddChannelReminderContext adds a reminder for a channel with a custom context +// NOTE: the ability to set reminders on a channel is currently undocumented but has been tested to work. +// Slack API docs: https://api.slack.com/methods/reminders.add +func (api *Client) AddChannelReminderContext(ctx context.Context, channelID, text, time string) (*Reminder, error) { values := url.Values{ "token": {api.token}, "text": {text}, "time": {time}, "channel": {channelID}, } - return api.doReminder(context.Background(), "reminders.add", values) + return api.doReminder(ctx, "reminders.add", values) } // AddUserReminder adds a reminder for a user. -// -// See https://api.slack.com/methods/reminders.add (NOTE: the ability to set -// reminders on a channel is currently undocumented but has been tested to -// work) +// For more details, see AddUserReminderContext documentation. func (api *Client) AddUserReminder(userID, text, time string) (*Reminder, error) { + return api.AddUserReminderContext(context.Background(), userID, text, time) +} + +// AddUserReminderContext adds a reminder for a user with a custom context +// Slack API docs: https://api.slack.com/methods/reminders.add +func (api *Client) AddUserReminderContext(ctx context.Context, userID, text, time string) (*Reminder, error) { values := url.Values{ "token": {api.token}, "text": {text}, "time": {time}, "user": {userID}, } - return api.doReminder(context.Background(), "reminders.add", values) + return api.doReminder(ctx, "reminders.add", values) } // DeleteReminder deletes an existing reminder. -// -// See https://api.slack.com/methods/reminders.delete +// For more details, see DeleteReminderContext documentation. func (api *Client) DeleteReminder(id string) error { + return api.DeleteReminderContext(context.Background(), id) +} + +// DeleteReminderContext deletes an existing reminder with a custom context +// Slack API docs: https://api.slack.com/methods/reminders.delete +func (api *Client) DeleteReminderContext(ctx context.Context, id string) error { values := url.Values{ "token": {api.token}, "reminder": {id}, } response := &SlackResponse{} - if err := api.postMethod(context.Background(), "reminders.delete", values, response); err != nil { + if err := api.postMethod(ctx, "reminders.delete", values, response); err != nil { return err } return response.Err() diff --git a/vendor/github.com/slack-go/slack/remotefiles.go b/vendor/github.com/slack-go/slack/remotefiles.go new file mode 100644 index 0000000..42639a1 --- /dev/null +++ b/vendor/github.com/slack-go/slack/remotefiles.go @@ -0,0 +1,309 @@ +package slack + +import ( + "context" + "fmt" + "io" + "net/url" + "strconv" + "strings" +) + +const ( + DEFAULT_REMOTE_FILES_CHANNEL = "" + DEFAULT_REMOTE_FILES_TS_FROM = 0 + DEFAULT_REMOTE_FILES_TS_TO = -1 + DEFAULT_REMOTE_FILES_COUNT = 100 +) + +// RemoteFile contains all the information for a remote file +// For more details: +// https://api.slack.com/messaging/files/remote +type RemoteFile struct { + ID string `json:"id"` + Created JSONTime `json:"created"` + Timestamp JSONTime `json:"timestamp"` + Name string `json:"name"` + Title string `json:"title"` + Mimetype string `json:"mimetype"` + Filetype string `json:"filetype"` + PrettyType string `json:"pretty_type"` + User string `json:"user"` + Editable bool `json:"editable"` + Size int `json:"size"` + Mode string `json:"mode"` + IsExternal bool `json:"is_external"` + ExternalType string `json:"external_type"` + IsPublic bool `json:"is_public"` + PublicURLShared bool `json:"public_url_shared"` + DisplayAsBot bool `json:"display_as_bot"` + Username string `json:"username"` + URLPrivate string `json:"url_private"` + Permalink string `json:"permalink"` + CommentsCount int `json:"comments_count"` + IsStarred bool `json:"is_starred"` + Shares Share `json:"shares"` + Channels []string `json:"channels"` + Groups []string `json:"groups"` + IMs []string `json:"ims"` + ExternalID string `json:"external_id"` + ExternalURL string `json:"external_url"` + HasRichPreview bool `json:"has_rich_preview"` +} + +// RemoteFileParameters contains required and optional parameters for a remote file. +// +// ExternalID is a user defined GUID, ExternalURL is where the remote file can be accessed, +// and Title is the name of the file. +// +// For more details: +// https://api.slack.com/methods/files.remote.add +type RemoteFileParameters struct { + ExternalID string // required + ExternalURL string // required + Title string // required + Filetype string + IndexableFileContents string + PreviewImage string + PreviewImageReader io.Reader +} + +// ListRemoteFilesParameters contains arguments for the ListRemoteFiles method. +// For more details: +// https://api.slack.com/methods/files.remote.list +type ListRemoteFilesParameters struct { + Channel string + Cursor string + Limit int + TimestampFrom JSONTime + TimestampTo JSONTime +} + +type remoteFileResponseFull struct { + RemoteFile `json:"file"` + Paging `json:"paging"` + Files []RemoteFile `json:"files"` + SlackResponse +} + +func (api *Client) remoteFileRequest(ctx context.Context, path string, values url.Values) (*remoteFileResponseFull, error) { + response := &remoteFileResponseFull{} + err := api.postMethod(ctx, path, values, response) + if err != nil { + return nil, err + } + + return response, response.Err() +} + +// AddRemoteFile adds a remote file. Unlike regular files, remote files must be explicitly shared. +// For more details see the AddRemoteFileContext documentation. +func (api *Client) AddRemoteFile(params RemoteFileParameters) (*RemoteFile, error) { + return api.AddRemoteFileContext(context.Background(), params) +} + +// AddRemoteFileContext adds a remote file and setting a custom context +// Slack API docs: https://api.slack.com/methods/files.remote.add +func (api *Client) AddRemoteFileContext(ctx context.Context, params RemoteFileParameters) (remotefile *RemoteFile, err error) { + if params.ExternalID == "" || params.ExternalURL == "" || params.Title == "" { + return nil, ErrParametersMissing + } + response := &remoteFileResponseFull{} + values := url.Values{ + "token": {api.token}, + "external_id": {params.ExternalID}, + "external_url": {params.ExternalURL}, + "title": {params.Title}, + } + if params.Filetype != "" { + values.Add("filetype", params.Filetype) + } + if params.IndexableFileContents != "" { + values.Add("indexable_file_contents", params.IndexableFileContents) + } + if params.PreviewImage != "" { + err = postLocalWithMultipartResponse(ctx, api.httpclient, api.endpoint+"files.remote.add", params.PreviewImage, "preview_image", api.token, values, response, api) + } else if params.PreviewImageReader != nil { + err = postWithMultipartResponse(ctx, api.httpclient, api.endpoint+"files.remote.add", "preview.png", "preview_image", api.token, values, params.PreviewImageReader, response, api) + } else { + response, err = api.remoteFileRequest(ctx, "files.remote.add", values) + } + + if err != nil { + return nil, err + } + + return &response.RemoteFile, response.Err() +} + +// ListRemoteFiles retrieves all remote files according to the parameters given. Uses cursor based pagination. +// For more details see the ListRemoteFilesContext documentation. +func (api *Client) ListRemoteFiles(params ListRemoteFilesParameters) ([]RemoteFile, error) { + return api.ListRemoteFilesContext(context.Background(), params) +} + +// ListRemoteFilesContext retrieves all remote files according to the parameters given with a custom context. Uses cursor based pagination. +// Slack API docs: https://api.slack.com/methods/files.remote.list +func (api *Client) ListRemoteFilesContext(ctx context.Context, params ListRemoteFilesParameters) ([]RemoteFile, error) { + values := url.Values{ + "token": {api.token}, + } + if params.Channel != DEFAULT_REMOTE_FILES_CHANNEL { + values.Add("channel", params.Channel) + } + if params.TimestampFrom != DEFAULT_REMOTE_FILES_TS_FROM { + values.Add("ts_from", strconv.FormatInt(int64(params.TimestampFrom), 10)) + } + if params.TimestampTo != DEFAULT_REMOTE_FILES_TS_TO { + values.Add("ts_to", strconv.FormatInt(int64(params.TimestampTo), 10)) + } + if params.Limit != DEFAULT_REMOTE_FILES_COUNT { + values.Add("limit", strconv.Itoa(params.Limit)) + } + if params.Cursor != "" { + values.Add("cursor", params.Cursor) + } + + response, err := api.remoteFileRequest(ctx, "files.remote.list", values) + if err != nil { + return nil, err + } + + params.Cursor = response.SlackResponse.ResponseMetadata.Cursor + + return response.Files, nil +} + +// GetRemoteFileInfo retrieves the complete remote file information. +// For more details see the GetRemoteFileInfoContext documentation. +func (api *Client) GetRemoteFileInfo(externalID, fileID string) (remotefile *RemoteFile, err error) { + return api.GetRemoteFileInfoContext(context.Background(), externalID, fileID) +} + +// GetRemoteFileInfoContext retrieves the complete remote file information given with a custom context. +// Slack API docs: https://api.slack.com/methods/files.remote.info +func (api *Client) GetRemoteFileInfoContext(ctx context.Context, externalID, fileID string) (remotefile *RemoteFile, err error) { + if fileID == "" && externalID == "" { + return nil, fmt.Errorf("either externalID or fileID is required") + } + if fileID != "" && externalID != "" { + return nil, fmt.Errorf("don't provide both externalID and fileID") + } + values := url.Values{ + "token": {api.token}, + } + if fileID != "" { + values.Add("file", fileID) + } + if externalID != "" { + values.Add("external_id", externalID) + } + response, err := api.remoteFileRequest(ctx, "files.remote.info", values) + if err != nil { + return nil, err + } + return &response.RemoteFile, err +} + +// ShareRemoteFile shares a remote file to channels. +// For more details see the ShareRemoteFileContext documentation. +func (api *Client) ShareRemoteFile(channels []string, externalID, fileID string) (file *RemoteFile, err error) { + return api.ShareRemoteFileContext(context.Background(), channels, externalID, fileID) +} + +// ShareRemoteFileContext shares a remote file to channels with a custom context. +// Slack API docs: https://api.slack.com/methods/files.remote.share +func (api *Client) ShareRemoteFileContext(ctx context.Context, channels []string, externalID, fileID string) (file *RemoteFile, err error) { + if channels == nil || len(channels) == 0 { + return nil, ErrParametersMissing + } + if fileID == "" && externalID == "" { + return nil, fmt.Errorf("either externalID or fileID is required") + } + values := url.Values{ + "token": {api.token}, + "channels": {strings.Join(channels, ",")}, + } + if fileID != "" { + values.Add("file", fileID) + } + if externalID != "" { + values.Add("external_id", externalID) + } + response, err := api.remoteFileRequest(ctx, "files.remote.share", values) + if err != nil { + return nil, err + } + return &response.RemoteFile, err +} + +// UpdateRemoteFile updates a remote file. +// For more details see the UpdateRemoteFileContext documentation. +func (api *Client) UpdateRemoteFile(fileID string, params RemoteFileParameters) (remotefile *RemoteFile, err error) { + return api.UpdateRemoteFileContext(context.Background(), fileID, params) +} + +// UpdateRemoteFileContext updates a remote file with a custom context. +// Slack API docs: https://api.slack.com/methods/files.remote.update +func (api *Client) UpdateRemoteFileContext(ctx context.Context, fileID string, params RemoteFileParameters) (remotefile *RemoteFile, err error) { + response := &remoteFileResponseFull{} + values := url.Values{} + if fileID != "" { + values.Add("file", fileID) + } + if params.ExternalID != "" { + values.Add("external_id", params.ExternalID) + } + if params.ExternalURL != "" { + values.Add("external_url", params.ExternalURL) + } + if params.Title != "" { + values.Add("title", params.Title) + } + if params.Filetype != "" { + values.Add("filetype", params.Filetype) + } + if params.IndexableFileContents != "" { + values.Add("indexable_file_contents", params.IndexableFileContents) + } + if params.PreviewImageReader != nil { + err = postWithMultipartResponse(ctx, api.httpclient, api.endpoint+"files.remote.update", "preview.png", "preview_image", api.token, values, params.PreviewImageReader, response, api) + } else { + values.Add("token", api.token) + response, err = api.remoteFileRequest(ctx, "files.remote.update", values) + } + + if err != nil { + return nil, err + } + + return &response.RemoteFile, response.Err() +} + +// RemoveRemoteFile removes a remote file. +// For more information see the RemoveRemoteFileContext documentation. +func (api *Client) RemoveRemoteFile(externalID, fileID string) (err error) { + return api.RemoveRemoteFileContext(context.Background(), externalID, fileID) +} + +// RemoveRemoteFileContext removes a remote file with a custom context +// Slack API docs: https://api.slack.com/methods/files.remote.remove +func (api *Client) RemoveRemoteFileContext(ctx context.Context, externalID, fileID string) (err error) { + if fileID == "" && externalID == "" { + return fmt.Errorf("either externalID or fileID is required") + } + if fileID != "" && externalID != "" { + return fmt.Errorf("don't provide both externalID and fileID") + } + values := url.Values{ + "token": {api.token}, + } + if fileID != "" { + values.Add("file", fileID) + } + if externalID != "" { + values.Add("external_id", externalID) + } + _, err = api.remoteFileRequest(ctx, "files.remote.remove", values) + return err +} diff --git a/vendor/github.com/slack-go/slack/search.go b/vendor/github.com/slack-go/slack/search.go index de6b40a..d27497a 100644 --- a/vendor/github.com/slack-go/slack/search.go +++ b/vendor/github.com/slack-go/slack/search.go @@ -15,6 +15,7 @@ const ( ) type SearchParameters struct { + TeamID string Sort string SortDirection string Highlight bool @@ -93,6 +94,9 @@ func (api *Client) _search(ctx context.Context, path, query string, params Searc "token": {api.token}, "query": {query}, } + if params.TeamID != "" { + values.Add("team_id", params.TeamID) + } if params.Sort != DEFAULT_SEARCH_SORT { values.Add("sort", params.Sort) } diff --git a/vendor/github.com/slack-go/slack/security.go b/vendor/github.com/slack-go/slack/security.go index dbe8fb2..4510352 100644 --- a/vendor/github.com/slack-go/slack/security.go +++ b/vendor/github.com/slack-go/slack/security.go @@ -20,6 +20,7 @@ const ( // SecretsVerifier contains the information needed to verify that the request comes from Slack type SecretsVerifier struct { + d Debug signature []byte hmac hash.Hash } @@ -75,6 +76,11 @@ func NewSecretsVerifier(header http.Header, secret string) (sv SecretsVerifier, return sv, err } +func (v *SecretsVerifier) WithDebug(d Debug) *SecretsVerifier { + v.d = d + return v +} + func (v *SecretsVerifier) Write(body []byte) (n int, err error) { return v.hmac.Write(body) } @@ -86,8 +92,10 @@ func (v SecretsVerifier) Ensure() error { if hmac.Equal(computed, v.signature) { return nil } - - return fmt.Errorf("Expected signing signature: %s, but computed: %s", hex.EncodeToString(v.signature), hex.EncodeToString(computed)) + if v.d != nil && v.d.Debug() { + v.d.Debugln(fmt.Sprintf("Expected signing signature: %s, but computed: %s", hex.EncodeToString(v.signature), hex.EncodeToString(computed))) + } + return fmt.Errorf("Computed unexpected signature of: %s", hex.EncodeToString(computed)) } func abs64(n int64) int64 { diff --git a/vendor/github.com/slack-go/slack/slack.go b/vendor/github.com/slack-go/slack/slack.go index d342a3e..756106f 100644 --- a/vendor/github.com/slack-go/slack/slack.go +++ b/vendor/github.com/slack-go/slack/slack.go @@ -23,7 +23,9 @@ type httpClient interface { // ResponseMetadata holds pagination metadata type ResponseMetadata struct { - Cursor string `json:"next_cursor"` + Cursor string `json:"next_cursor"` + Messages []string `json:"messages"` + Warnings []string `json:"warnings"` } func (t *ResponseMetadata) initialize() *ResponseMetadata { @@ -43,6 +45,7 @@ type AuthTestResponse struct { UserID string `json:"user_id"` // EnterpriseID is only returned when an enterprise id present EnterpriseID string `json:"enterprise_id,omitempty"` + BotID string `json:"bot_id"` } type authTestResponseFull struct { @@ -54,11 +57,14 @@ type authTestResponseFull struct { type ParamOption func(*url.Values) type Client struct { - token string - endpoint string - debug bool - log ilogger - httpclient httpClient + token string + appLevelToken string + configToken string + configRefreshToken string + endpoint string + debug bool + log ilogger + httpclient httpClient } // Option defines an option for a Client @@ -90,6 +96,21 @@ func OptionAPIURL(u string) func(*Client) { return func(c *Client) { c.endpoint = u } } +// OptionAppLevelToken sets an app-level token for the client. +func OptionAppLevelToken(token string) func(*Client) { + return func(c *Client) { c.appLevelToken = token } +} + +// OptionConfigToken sets a configuration token for the client. +func OptionConfigToken(token string) func(*Client) { + return func(c *Client) { c.configToken = token } +} + +// OptionConfigRefreshToken sets a configuration refresh token for the client. +func OptionConfigRefreshToken(token string) func(*Client) { + return func(c *Client) { c.configRefreshToken = token } +} + // New builds a slack client from the provided token and options. func New(token string, options ...Option) *Client { s := &Client{ @@ -148,6 +169,6 @@ func (api *Client) postMethod(ctx context.Context, path string, values url.Value } // get a slack web method. -func (api *Client) getMethod(ctx context.Context, path string, values url.Values, intf interface{}) error { - return getResource(ctx, api.httpclient, api.endpoint+path, values, intf, api) +func (api *Client) getMethod(ctx context.Context, path string, token string, values url.Values, intf interface{}) error { + return getResource(ctx, api.httpclient, api.endpoint+path, token, values, intf, api) } diff --git a/vendor/github.com/slack-go/slack/slackutilsx/slackutilsx.go b/vendor/github.com/slack-go/slack/slackutilsx/slackutilsx.go index 1f7b2b8..d6c9c07 100644 --- a/vendor/github.com/slack-go/slack/slackutilsx/slackutilsx.go +++ b/vendor/github.com/slack-go/slack/slackutilsx/slackutilsx.go @@ -50,10 +50,12 @@ func DetectChannelType(channelID string) ChannelType { } } +// initialize replacer only once (if needed) +var escapeReplacer = strings.NewReplacer("&", "&", "<", "<", ">", ">") + // EscapeMessage text func EscapeMessage(message string) string { - replacer := strings.NewReplacer("&", "&", "<", "<", ">", ">") - return replacer.Replace(message) + return escapeReplacer.Replace(message) } // Retryable errors return true. diff --git a/vendor/github.com/slack-go/slack/slash.go b/vendor/github.com/slack-go/slack/slash.go index f62065a..fd46abf 100644 --- a/vendor/github.com/slack-go/slack/slash.go +++ b/vendor/github.com/slack-go/slack/slash.go @@ -1,24 +1,29 @@ package slack import ( + "encoding/json" + "fmt" "net/http" + "strconv" ) // SlashCommand contains information about a request of the slash command type SlashCommand struct { - Token string `json:"token"` - TeamID string `json:"team_id"` - TeamDomain string `json:"team_domain"` - EnterpriseID string `json:"enterprise_id,omitempty"` - EnterpriseName string `json:"enterprise_name,omitempty"` - ChannelID string `json:"channel_id"` - ChannelName string `json:"channel_name"` - UserID string `json:"user_id"` - UserName string `json:"user_name"` - Command string `json:"command"` - Text string `json:"text"` - ResponseURL string `json:"response_url"` - TriggerID string `json:"trigger_id"` + Token string `json:"token"` + TeamID string `json:"team_id"` + TeamDomain string `json:"team_domain"` + EnterpriseID string `json:"enterprise_id,omitempty"` + EnterpriseName string `json:"enterprise_name,omitempty"` + IsEnterpriseInstall bool `json:"is_enterprise_install"` + ChannelID string `json:"channel_id"` + ChannelName string `json:"channel_name"` + UserID string `json:"user_id"` + UserName string `json:"user_name"` + Command string `json:"command"` + Text string `json:"text"` + ResponseURL string `json:"response_url"` + TriggerID string `json:"trigger_id"` + APIAppID string `json:"api_app_id"` } // SlashCommandParse will parse the request of the slash command @@ -31,6 +36,7 @@ func SlashCommandParse(r *http.Request) (s SlashCommand, err error) { s.TeamDomain = r.PostForm.Get("team_domain") s.EnterpriseID = r.PostForm.Get("enterprise_id") s.EnterpriseName = r.PostForm.Get("enterprise_name") + s.IsEnterpriseInstall = r.PostForm.Get("is_enterprise_install") == "true" s.ChannelID = r.PostForm.Get("channel_id") s.ChannelName = r.PostForm.Get("channel_name") s.UserID = r.PostForm.Get("user_id") @@ -39,6 +45,7 @@ func SlashCommandParse(r *http.Request) (s SlashCommand, err error) { s.Text = r.PostForm.Get("text") s.ResponseURL = r.PostForm.Get("response_url") s.TriggerID = r.PostForm.Get("trigger_id") + s.APIAppID = r.PostForm.Get("api_app_id") return s, nil } @@ -51,3 +58,34 @@ func (s SlashCommand) ValidateToken(verificationTokens ...string) bool { } return false } + +// UnmarshalJSON handles is_enterprise_install being either a boolean or a +// string when parsing JSON from various payloads +func (s *SlashCommand) UnmarshalJSON(data []byte) error { + type SlashCommandCopy SlashCommand + scopy := &struct { + *SlashCommandCopy + IsEnterpriseInstall interface{} `json:"is_enterprise_install"` + }{ + SlashCommandCopy: (*SlashCommandCopy)(s), + } + + if err := json.Unmarshal(data, scopy); err != nil { + return err + } + + switch rawValue := scopy.IsEnterpriseInstall.(type) { + case string: + b, err := strconv.ParseBool(rawValue) + if err != nil { + return fmt.Errorf("parsing boolean for is_enterprise_install: %w", err) + } + s.IsEnterpriseInstall = b + case bool: + s.IsEnterpriseInstall = rawValue + default: + return fmt.Errorf("wrong data type for is_enterprise_install: %T", scopy.IsEnterpriseInstall) + } + + return nil +} diff --git a/vendor/github.com/slack-go/slack/socket_mode.go b/vendor/github.com/slack-go/slack/socket_mode.go new file mode 100644 index 0000000..69e40d9 --- /dev/null +++ b/vendor/github.com/slack-go/slack/socket_mode.go @@ -0,0 +1,34 @@ +package slack + +import ( + "context" +) + +// SocketModeConnection contains various details about the SocketMode connection. +// It is returned by an "apps.connections.open" API call. +type SocketModeConnection struct { + URL string `json:"url,omitempty"` + Data map[string]interface{} `json:"-"` +} + +type openResponseFull struct { + SlackResponse + SocketModeConnection +} + +// StartSocketModeContext calls the "apps.connections.open" endpoint and returns the provided URL and the full Info block with a custom context. +// +// To have a fully managed Socket Mode connection, use `socketmode.New()`, and call `Run()` on it. +func (api *Client) StartSocketModeContext(ctx context.Context) (info *SocketModeConnection, websocketURL string, err error) { + response := &openResponseFull{} + err = postJSON(ctx, api.httpclient, api.endpoint+"apps.connections.open", api.appLevelToken, nil, response, api) + if err != nil { + return nil, "", err + } + + if response.Err() == nil { + api.Debugln("Using URL:", response.SocketModeConnection.URL) + } + + return &response.SocketModeConnection, response.SocketModeConnection.URL, response.Err() +} diff --git a/vendor/github.com/slack-go/slack/stars.go b/vendor/github.com/slack-go/slack/stars.go index 5296760..5192685 100644 --- a/vendor/github.com/slack-go/slack/stars.go +++ b/vendor/github.com/slack-go/slack/stars.go @@ -36,12 +36,14 @@ func NewStarsParameters() StarsParameters { } } -// AddStar stars an item in a channel +// AddStar stars an item in a channel. +// For more information see the AddStarContext documentation. func (api *Client) AddStar(channel string, item ItemRef) error { return api.AddStarContext(context.Background(), channel, item) } -// AddStarContext stars an item in a channel with a custom context +// AddStarContext stars an item in a channel with a custom context. +// Slack API docs: https://api.slack.com/methods/stars.add func (api *Client) AddStarContext(ctx context.Context, channel string, item ItemRef) error { values := url.Values{ "channel": {channel}, @@ -65,12 +67,14 @@ func (api *Client) AddStarContext(ctx context.Context, channel string, item Item return response.Err() } -// RemoveStar removes a starred item from a channel +// RemoveStar removes a starred item from a channel. +// For more information see the RemoveStarContext documentation. func (api *Client) RemoveStar(channel string, item ItemRef) error { return api.RemoveStarContext(context.Background(), channel, item) } -// RemoveStarContext removes a starred item from a channel with a custom context +// RemoveStarContext removes a starred item from a channel with a custom context. +// Slack API docs: https://api.slack.com/methods/stars.remove func (api *Client) RemoveStarContext(ctx context.Context, channel string, item ItemRef) error { values := url.Values{ "channel": {channel}, @@ -94,12 +98,14 @@ func (api *Client) RemoveStarContext(ctx context.Context, channel string, item I return response.Err() } -// ListStars returns information about the stars a user added +// ListStars returns information about the stars a user added. +// For more information see the ListStarsContext documentation. func (api *Client) ListStars(params StarsParameters) ([]Item, *Paging, error) { return api.ListStarsContext(context.Background(), params) } -// ListStarsContext returns information about the stars a user added with a custom context +// ListStarsContext returns information about the stars a user added with a custom context. +// Slack API docs: https://api.slack.com/methods/stars.list func (api *Client) ListStarsContext(ctx context.Context, params StarsParameters) ([]Item, *Paging, error) { values := url.Values{ "token": {api.token}, @@ -130,23 +136,23 @@ func (api *Client) ListStarsContext(ctx context.Context, params StarsParameters) // GetStarred returns a list of StarredItem items. // // The user then has to iterate over them and figure out what they should -// be looking at according to what is in the Type. -// for _, item := range items { -// switch c.Type { -// case "file_comment": -// log.Println(c.Comment) -// case "file": -// ... +// be looking at according to what is in the Type: +// +// for _, item := range items { +// switch c.Type { +// case "file_comment": +// log.Println(c.Comment) +// case "file": +// ... +// } // -// } // This function still exists to maintain backwards compatibility. -// I exposed it as returning []StarredItem, so it shall stay as StarredItem +// I exposed it as returning []StarredItem, so it shall stay as StarredItem. func (api *Client) GetStarred(params StarsParameters) ([]StarredItem, *Paging, error) { return api.GetStarredContext(context.Background(), params) } // GetStarredContext returns a list of StarredItem items with a custom context -// // For more details see GetStarred func (api *Client) GetStarredContext(ctx context.Context, params StarsParameters) ([]StarredItem, *Paging, error) { items, paging, err := api.ListStarsContext(ctx, params) diff --git a/vendor/github.com/slack-go/slack/status_code_error.go b/vendor/github.com/slack-go/slack/status_code_error.go new file mode 100644 index 0000000..7347137 --- /dev/null +++ b/vendor/github.com/slack-go/slack/status_code_error.go @@ -0,0 +1,28 @@ +package slack + +import ( + "fmt" + "net/http" +) + +// StatusCodeError represents an http response error. +// type httpStatusCode interface { HTTPStatusCode() int } to handle it. +type StatusCodeError struct { + Code int + Status string +} + +func (t StatusCodeError) Error() string { + return fmt.Sprintf("slack server error: %s", t.Status) +} + +func (t StatusCodeError) HTTPStatusCode() int { + return t.Code +} + +func (t StatusCodeError) Retryable() bool { + if t.Code >= 500 || t.Code == http.StatusTooManyRequests { + return true + } + return false +} diff --git a/vendor/github.com/slack-go/slack/team.go b/vendor/github.com/slack-go/slack/team.go index 029e2b5..4e890c2 100644 --- a/vendor/github.com/slack-go/slack/team.go +++ b/vendor/github.com/slack-go/slack/team.go @@ -24,6 +24,26 @@ type TeamInfo struct { Icon map[string]interface{} `json:"icon"` } +type TeamProfileResponse struct { + Profile TeamProfile `json:"profile"` + SlackResponse +} + +type TeamProfile struct { + Fields []TeamProfileField `json:"fields"` +} + +type TeamProfileField struct { + ID string `json:"id"` + Ordering int `json:"ordering"` + Label string `json:"label"` + Hint string `json:"hint"` + Type string `json:"type"` + PossibleValues []string `json:"possible_values"` + IsHidden bool `json:"is_hidden"` + Options map[string]bool `json:"options"` +} + type LoginResponse struct { Logins []Login `json:"logins"` Paging `json:"paging"` @@ -54,8 +74,9 @@ type BillingActive struct { // AccessLogParameters contains all the parameters necessary (including the optional ones) for a GetAccessLogs() request type AccessLogParameters struct { - Count int - Page int + TeamID string + Count int + Page int } // NewAccessLogParameters provides an instance of AccessLogParameters with all the sane default values set @@ -95,12 +116,46 @@ func (api *Client) accessLogsRequest(ctx context.Context, path string, values ur return response, response.Err() } -// GetTeamInfo gets the Team Information of the user +func (api *Client) teamProfileRequest(ctx context.Context, client httpClient, path string, values url.Values) (*TeamProfileResponse, error) { + response := &TeamProfileResponse{} + err := api.postMethod(ctx, path, values, response) + if err != nil { + return nil, err + } + return response, response.Err() +} + +// GetTeamInfo gets the Team Information of the user. +// For more information see the GetTeamInfoContext documentation. func (api *Client) GetTeamInfo() (*TeamInfo, error) { return api.GetTeamInfoContext(context.Background()) } -// GetTeamInfoContext gets the Team Information of the user with a custom context +// GetOtherTeamInfoContext gets Team information for any team with a custom context. +// Slack API docs: https://api.slack.com/methods/team.info +func (api *Client) GetOtherTeamInfoContext(ctx context.Context, team string) (*TeamInfo, error) { + if team == "" { + return api.GetTeamInfoContext(ctx) + } + values := url.Values{ + "token": {api.token}, + } + values.Add("team", team) + response, err := api.teamRequest(ctx, "team.info", values) + if err != nil { + return nil, err + } + return &response.Team, nil +} + +// GetOtherTeamInfo gets Team information for any team. +// For more information see the GetOtherTeamInfoContext documentation. +func (api *Client) GetOtherTeamInfo(team string) (*TeamInfo, error) { + return api.GetOtherTeamInfoContext(context.Background(), team) +} + +// GetTeamInfoContext gets the Team Information of the user with a custom context. +// Slack API docs: https://api.slack.com/methods/team.info func (api *Client) GetTeamInfoContext(ctx context.Context) (*TeamInfo, error) { values := url.Values{ "token": {api.token}, @@ -113,16 +168,44 @@ func (api *Client) GetTeamInfoContext(ctx context.Context) (*TeamInfo, error) { return &response.Team, nil } -// GetAccessLogs retrieves a page of logins according to the parameters given +// GetTeamProfile gets the Team Profile settings of the user. +// For more information see the GetTeamProfileContext documentation. +func (api *Client) GetTeamProfile(teamID ...string) (*TeamProfile, error) { + return api.GetTeamProfileContext(context.Background(), teamID...) +} + +// GetTeamProfileContext gets the Team Profile settings of the user with a custom context. +// Slack API docs: https://api.slack.com/methods/team.profile.get +func (api *Client) GetTeamProfileContext(ctx context.Context, teamID ...string) (*TeamProfile, error) { + values := url.Values{ + "token": {api.token}, + } + if len(teamID) > 0 { + values["team_id"] = teamID + } + + response, err := api.teamProfileRequest(ctx, api.httpclient, "team.profile.get", values) + if err != nil { + return nil, err + } + return &response.Profile, nil +} + +// GetAccessLogs retrieves a page of logins according to the parameters given. +// For more information see the GetAccessLogsContext documentation. func (api *Client) GetAccessLogs(params AccessLogParameters) ([]Login, *Paging, error) { return api.GetAccessLogsContext(context.Background(), params) } -// GetAccessLogsContext retrieves a page of logins according to the parameters given with a custom context +// GetAccessLogsContext retrieves a page of logins according to the parameters given with a custom context. +// Slack API docs: https://api.slack.com/methods/team.accessLogs func (api *Client) GetAccessLogsContext(ctx context.Context, params AccessLogParameters) ([]Login, *Paging, error) { values := url.Values{ "token": {api.token}, } + if params.TeamID != "" { + values.Add("team_id", params.TeamID) + } if params.Count != DEFAULT_LOGINS_COUNT { values.Add("count", strconv.Itoa(params.Count)) } @@ -137,30 +220,30 @@ func (api *Client) GetAccessLogsContext(ctx context.Context, params AccessLogPar return response.Logins, &response.Paging, nil } -// GetBillableInfo ... -func (api *Client) GetBillableInfo(user string) (map[string]BillingActive, error) { - return api.GetBillableInfoContext(context.Background(), user) +type GetBillableInfoParams struct { + User string + TeamID string } -// GetBillableInfoContext ... -func (api *Client) GetBillableInfoContext(ctx context.Context, user string) (map[string]BillingActive, error) { +// GetBillableInfo gets the billable users information of the team. +// For more information see the GetBillableInfoContext documentation. +func (api *Client) GetBillableInfo(params GetBillableInfoParams) (map[string]BillingActive, error) { + return api.GetBillableInfoContext(context.Background(), params) +} + +// GetBillableInfoContext gets the billable users information of the team with a custom context. +// Slack API docs: https://api.slack.com/methods/team.billableInfo +func (api *Client) GetBillableInfoContext(ctx context.Context, params GetBillableInfoParams) (map[string]BillingActive, error) { values := url.Values{ "token": {api.token}, - "user": {user}, } - return api.billableInfoRequest(ctx, "team.billableInfo", values) -} - -// GetBillableInfoForTeam returns the billing_active status of all users on the team. -func (api *Client) GetBillableInfoForTeam() (map[string]BillingActive, error) { - return api.GetBillableInfoForTeamContext(context.Background()) -} + if params.TeamID != "" { + values.Add("team_id", params.TeamID) + } -// GetBillableInfoForTeamContext returns the billing_active status of all users on the team with a custom context -func (api *Client) GetBillableInfoForTeamContext(ctx context.Context) (map[string]BillingActive, error) { - values := url.Values{ - "token": {api.token}, + if params.User != "" { + values.Add("user", params.User) } return api.billableInfoRequest(ctx, "team.billableInfo", values) diff --git a/vendor/github.com/slack-go/slack/tokens.go b/vendor/github.com/slack-go/slack/tokens.go new file mode 100644 index 0000000..49bbde9 --- /dev/null +++ b/vendor/github.com/slack-go/slack/tokens.go @@ -0,0 +1,52 @@ +package slack + +import ( + "context" + "net/url" +) + +// RotateTokens exchanges a refresh token for a new app configuration token. +// For more information see the RotateTokensContext documentation. +func (api *Client) RotateTokens(configToken string, refreshToken string) (*TokenResponse, error) { + return api.RotateTokensContext(context.Background(), configToken, refreshToken) +} + +// RotateTokensContext exchanges a refresh token for a new app configuration token with a custom context. +// Slack API docs: https://api.slack.com/methods/tooling.tokens.rotate +func (api *Client) RotateTokensContext(ctx context.Context, configToken string, refreshToken string) (*TokenResponse, error) { + if configToken == "" { + configToken = api.configToken + } + + if refreshToken == "" { + refreshToken = api.configRefreshToken + } + + values := url.Values{ + "refresh_token": {refreshToken}, + } + + response := &TokenResponse{} + err := api.getMethod(ctx, "tooling.tokens.rotate", configToken, values, response) + if err != nil { + return nil, err + } + + return response, response.Err() +} + +// UpdateConfigTokens replaces the configuration tokens in the client with those returned by the API +func (api *Client) UpdateConfigTokens(response *TokenResponse) { + api.configToken = response.Token + api.configRefreshToken = response.RefreshToken +} + +type TokenResponse struct { + Token string `json:"token,omitempty"` + RefreshToken string `json:"refresh_token,omitempty"` + TeamId string `json:"team_id,omitempty"` + UserId string `json:"user_id,omitempty"` + IssuedAt uint64 `json:"iat,omitempty"` + ExpiresAt uint64 `json:"exp,omitempty"` + SlackResponse +} diff --git a/vendor/github.com/slack-go/slack/usergroups.go b/vendor/github.com/slack-go/slack/usergroups.go index 9417f81..0c2ec4c 100644 --- a/vendor/github.com/slack-go/slack/usergroups.go +++ b/vendor/github.com/slack-go/slack/usergroups.go @@ -50,18 +50,24 @@ func (api *Client) userGroupRequest(ctx context.Context, path string, values url return response, response.Err() } -// CreateUserGroup creates a new user group +// CreateUserGroup creates a new user group. +// For more information see the CreateUserGroupContext documentation. func (api *Client) CreateUserGroup(userGroup UserGroup) (UserGroup, error) { return api.CreateUserGroupContext(context.Background(), userGroup) } -// CreateUserGroupContext creates a new user group with a custom context +// CreateUserGroupContext creates a new user group with a custom context. +// Slack API docs: https://api.slack.com/methods/usergroups.create func (api *Client) CreateUserGroupContext(ctx context.Context, userGroup UserGroup) (UserGroup, error) { values := url.Values{ "token": {api.token}, "name": {userGroup.Name}, } + if userGroup.TeamID != "" { + values["team_id"] = []string{userGroup.TeamID} + } + if userGroup.Handle != "" { values["handle"] = []string{userGroup.Handle} } @@ -81,12 +87,14 @@ func (api *Client) CreateUserGroupContext(ctx context.Context, userGroup UserGro return response.UserGroup, nil } -// DisableUserGroup disables an existing user group +// DisableUserGroup disables an existing user group. +// For more information see the DisableUserGroupContext documentation. func (api *Client) DisableUserGroup(userGroup string) (UserGroup, error) { return api.DisableUserGroupContext(context.Background(), userGroup) } -// DisableUserGroupContext disables an existing user group with a custom context +// DisableUserGroupContext disables an existing user group with a custom context. +// Slack API docs: https://api.slack.com/methods/usergroups.disable func (api *Client) DisableUserGroupContext(ctx context.Context, userGroup string) (UserGroup, error) { values := url.Values{ "token": {api.token}, @@ -100,12 +108,14 @@ func (api *Client) DisableUserGroupContext(ctx context.Context, userGroup string return response.UserGroup, nil } -// EnableUserGroup enables an existing user group +// EnableUserGroup enables an existing user group. +// For more information see the EnableUserGroupContext documentation. func (api *Client) EnableUserGroup(userGroup string) (UserGroup, error) { return api.EnableUserGroupContext(context.Background(), userGroup) } -// EnableUserGroupContext enables an existing user group with a custom context +// EnableUserGroupContext enables an existing user group with a custom context. +// Slack API docs: https://api.slack.com/methods/usergroups.enable func (api *Client) EnableUserGroupContext(ctx context.Context, userGroup string) (UserGroup, error) { values := url.Values{ "token": {api.token}, @@ -122,6 +132,12 @@ func (api *Client) EnableUserGroupContext(ctx context.Context, userGroup string) // GetUserGroupsOption options for the GetUserGroups method call. type GetUserGroupsOption func(*GetUserGroupsParams) +func GetUserGroupsOptionWithTeamID(teamID string) GetUserGroupsOption { + return func(params *GetUserGroupsParams) { + params.TeamID = teamID + } +} + // GetUserGroupsOptionIncludeCount include the number of users in each User Group (default: false) func GetUserGroupsOptionIncludeCount(b bool) GetUserGroupsOption { return func(params *GetUserGroupsParams) { @@ -145,17 +161,20 @@ func GetUserGroupsOptionIncludeUsers(b bool) GetUserGroupsOption { // GetUserGroupsParams contains arguments for GetUserGroups method call type GetUserGroupsParams struct { + TeamID string IncludeCount bool IncludeDisabled bool IncludeUsers bool } -// GetUserGroups returns a list of user groups for the team +// GetUserGroups returns a list of user groups for the team. +// For more information see the GetUserGroupsContext documentation. func (api *Client) GetUserGroups(options ...GetUserGroupsOption) ([]UserGroup, error) { return api.GetUserGroupsContext(context.Background(), options...) } -// GetUserGroupsContext returns a list of user groups for the team with a custom context +// GetUserGroupsContext returns a list of user groups for the team with a custom context. +// Slack API docs: https://api.slack.com/methods/usergroups.list func (api *Client) GetUserGroupsContext(ctx context.Context, options ...GetUserGroupsOption) ([]UserGroup, error) { params := GetUserGroupsParams{} @@ -166,6 +185,9 @@ func (api *Client) GetUserGroupsContext(ctx context.Context, options ...GetUserG values := url.Values{ "token": {api.token}, } + if params.TeamID != "" { + values.Add("team_id", params.TeamID) + } if params.IncludeCount { values.Add("include_count", "true") } @@ -183,32 +205,79 @@ func (api *Client) GetUserGroupsContext(ctx context.Context, options ...GetUserG return response.UserGroups, nil } -// UpdateUserGroup will update an existing user group -func (api *Client) UpdateUserGroup(userGroup UserGroup) (UserGroup, error) { - return api.UpdateUserGroupContext(context.Background(), userGroup) +// UpdateUserGroupsOption options for the UpdateUserGroup method call. +type UpdateUserGroupsOption func(*UpdateUserGroupsParams) + +// UpdateUserGroupsOptionName change the name of the User Group (default: empty, so it's no-op) +func UpdateUserGroupsOptionName(name string) UpdateUserGroupsOption { + return func(params *UpdateUserGroupsParams) { + params.Name = name + } } -// UpdateUserGroupContext will update an existing user group with a custom context -func (api *Client) UpdateUserGroupContext(ctx context.Context, userGroup UserGroup) (UserGroup, error) { +// UpdateUserGroupsOptionHandle change the handle of the User Group (default: empty, so it's no-op) +func UpdateUserGroupsOptionHandle(handle string) UpdateUserGroupsOption { + return func(params *UpdateUserGroupsParams) { + params.Handle = handle + } +} + +// UpdateUserGroupsOptionDescription change the description of the User Group. (default: nil, so it's no-op) +func UpdateUserGroupsOptionDescription(description *string) UpdateUserGroupsOption { + return func(params *UpdateUserGroupsParams) { + params.Description = description + } +} + +// UpdateUserGroupsOptionChannels change the default channels of the User Group. (default: unspecified, so it's no-op) +func UpdateUserGroupsOptionChannels(channels []string) UpdateUserGroupsOption { + return func(params *UpdateUserGroupsParams) { + params.Channels = &channels + } +} + +// UpdateUserGroupsParams contains arguments for UpdateUserGroup method call +type UpdateUserGroupsParams struct { + Name string + Handle string + Description *string + Channels *[]string +} + +// UpdateUserGroup will update an existing user group. +// For more information see the UpdateUserGroupContext documentation. +func (api *Client) UpdateUserGroup(userGroupID string, options ...UpdateUserGroupsOption) (UserGroup, error) { + return api.UpdateUserGroupContext(context.Background(), userGroupID, options...) +} + +// UpdateUserGroupContext will update an existing user group with a custom context. +// Slack API docs: https://api.slack.com/methods/usergroups.update +func (api *Client) UpdateUserGroupContext(ctx context.Context, userGroupID string, options ...UpdateUserGroupsOption) (UserGroup, error) { + params := UpdateUserGroupsParams{} + + for _, opt := range options { + opt(¶ms) + } + values := url.Values{ "token": {api.token}, - "usergroup": {userGroup.ID}, + "usergroup": {userGroupID}, } - if userGroup.Name != "" { - values["name"] = []string{userGroup.Name} + if params.Name != "" { + values["name"] = []string{params.Name} } - if userGroup.Handle != "" { - values["handle"] = []string{userGroup.Handle} + if params.Handle != "" { + values["handle"] = []string{params.Handle} } - if userGroup.Description != "" { - values["description"] = []string{userGroup.Description} + if params.Description != nil { + values["description"] = []string{*params.Description} } - if len(userGroup.Prefs.Channels) > 0 { - values["channels"] = []string{strings.Join(userGroup.Prefs.Channels, ",")} + if params.Channels != nil { + values["channels"] = []string{strings.Join(*params.Channels, ",")} } response, err := api.userGroupRequest(ctx, "usergroups.update", values) @@ -218,12 +287,14 @@ func (api *Client) UpdateUserGroupContext(ctx context.Context, userGroup UserGro return response.UserGroup, nil } -// GetUserGroupMembers will retrieve the current list of users in a group +// GetUserGroupMembers will retrieve the current list of users in a group. +// For more information see the GetUserGroupMembersContext documentation. func (api *Client) GetUserGroupMembers(userGroup string) ([]string, error) { return api.GetUserGroupMembersContext(context.Background(), userGroup) } -// GetUserGroupMembersContext will retrieve the current list of users in a group with a custom context +// GetUserGroupMembersContext will retrieve the current list of users in a group with a custom context. +// Slack API docs: https://api.slack.com/methods/usergroups.users.list func (api *Client) GetUserGroupMembersContext(ctx context.Context, userGroup string) ([]string, error) { values := url.Values{ "token": {api.token}, @@ -237,12 +308,14 @@ func (api *Client) GetUserGroupMembersContext(ctx context.Context, userGroup str return response.Users, nil } -// UpdateUserGroupMembers will update the members of an existing user group +// UpdateUserGroupMembers will update the members of an existing user group. +// For more information see the UpdateUserGroupMembersContext documentation. func (api *Client) UpdateUserGroupMembers(userGroup string, members string) (UserGroup, error) { return api.UpdateUserGroupMembersContext(context.Background(), userGroup, members) } -// UpdateUserGroupMembersContext will update the members of an existing user group with a custom context +// UpdateUserGroupMembersContext will update the members of an existing user group with a custom context. +// Slack API docs: https://api.slack.com/methods/usergroups.update func (api *Client) UpdateUserGroupMembersContext(ctx context.Context, userGroup string, members string) (UserGroup, error) { values := url.Values{ "token": {api.token}, diff --git a/vendor/github.com/slack-go/slack/users.go b/vendor/github.com/slack-go/slack/users.go index 6001e0f..b51b172 100644 --- a/vendor/github.com/slack-go/slack/users.go +++ b/vendor/github.com/slack-go/slack/users.go @@ -5,6 +5,7 @@ import ( "encoding/json" "net/url" "strconv" + "strings" "time" ) @@ -16,29 +17,39 @@ const ( // UserProfile contains all the information details of a given user type UserProfile struct { - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - RealName string `json:"real_name"` - RealNameNormalized string `json:"real_name_normalized"` - DisplayName string `json:"display_name"` - DisplayNameNormalized string `json:"display_name_normalized"` - Email string `json:"email"` - Skype string `json:"skype"` - Phone string `json:"phone"` - Image24 string `json:"image_24"` - Image32 string `json:"image_32"` - Image48 string `json:"image_48"` - Image72 string `json:"image_72"` - Image192 string `json:"image_192"` - ImageOriginal string `json:"image_original"` - Title string `json:"title"` - BotID string `json:"bot_id,omitempty"` - ApiAppID string `json:"api_app_id,omitempty"` - StatusText string `json:"status_text,omitempty"` - StatusEmoji string `json:"status_emoji,omitempty"` - StatusExpiration int `json:"status_expiration"` - Team string `json:"team"` - Fields UserProfileCustomFields `json:"fields"` + FirstName string `json:"first_name,omitempty"` + LastName string `json:"last_name,omitempty"` + RealName string `json:"real_name"` + RealNameNormalized string `json:"real_name_normalized"` + DisplayName string `json:"display_name"` + DisplayNameNormalized string `json:"display_name_normalized"` + AvatarHash string `json:"avatar_hash"` + Email string `json:"email,omitempty"` + Skype string `json:"skyp,omitempty"` + Phone string `json:"phone,omitempty"` + Image24 string `json:"image_24"` + Image32 string `json:"image_32"` + Image48 string `json:"image_48"` + Image72 string `json:"image_72"` + Image192 string `json:"image_192"` + Image512 string `json:"image_512"` + ImageOriginal string `json:"image_original,omitempty"` + Title string `json:"title,omitempty"` + BotID string `json:"bot_id,omitempty"` + ApiAppID string `json:"api_app_id,omitempty"` + StatusText string `json:"status_text,omitempty"` + StatusEmoji string `json:"status_emoji,omitempty"` + StatusEmojiDisplayInfo []UserProfileStatusEmojiDisplayInfo `json:"status_emoji_display_info,omitempty"` + StatusExpiration int `json:"status_expiration,omitempty"` + Team string `json:"team"` + Fields UserProfileCustomFields `json:"fields,omitempty"` +} + +type UserProfileStatusEmojiDisplayInfo struct { + EmojiName string `json:"emoji_name"` + DisplayAlias string `json:"display_alias,omitempty"` + DisplayURL string `json:"display_url,omitempty"` + Unicode string `json:"unicode,omitempty"` } // UserProfileCustomFields represents user profile's custom fields. @@ -120,6 +131,7 @@ type User struct { IsAppUser bool `json:"is_app_user"` IsInvitedUser bool `json:"is_invited_user"` Has2FA bool `json:"has_2fa"` + TwoFactorType *string `json:"two_factor_type"` HasFiles bool `json:"has_files"` Presence string `json:"presence"` Locale string `json:"locale"` @@ -184,6 +196,7 @@ type TeamIdentity struct { type userResponseFull struct { Members []User `json:"members,omitempty"` User `json:"user,omitempty"` + Users []User `json:"users,omitempty"` UserPresence SlackResponse Metadata ResponseMetadata `json:"response_metadata"` @@ -214,11 +227,13 @@ func (api *Client) userRequest(ctx context.Context, path string, values url.Valu } // GetUserPresence will retrieve the current presence status of given user. +// For more information see the GetUserPresenceContext documentation. func (api *Client) GetUserPresence(user string) (*UserPresence, error) { return api.GetUserPresenceContext(context.Background(), user) } // GetUserPresenceContext will retrieve the current presence status of given user with a custom context. +// Slack API docs: https://api.slack.com/methods/users.getPresence func (api *Client) GetUserPresenceContext(ctx context.Context, user string) (*UserPresence, error) { values := url.Values{ "token": {api.token}, @@ -232,12 +247,14 @@ func (api *Client) GetUserPresenceContext(ctx context.Context, user string) (*Us return &response.UserPresence, nil } -// GetUserInfo will retrieve the complete user information +// GetUserInfo will retrieve the complete user information. +// For more information see the GetUserInfoContext documentation. func (api *Client) GetUserInfo(user string) (*User, error) { return api.GetUserInfoContext(context.Background(), user) } -// GetUserInfoContext will retrieve the complete user information with a custom context +// GetUserInfoContext will retrieve the complete user information with a custom context. +// Slack API docs: https://api.slack.com/methods/users.info func (api *Client) GetUserInfoContext(ctx context.Context, user string) (*User, error) { values := url.Values{ "token": {api.token}, @@ -252,6 +269,28 @@ func (api *Client) GetUserInfoContext(ctx context.Context, user string) (*User, return &response.User, nil } +// GetUsersInfo will retrieve the complete multi-users information. +// For more information see the GetUsersInfoContext documentation. +func (api *Client) GetUsersInfo(users ...string) (*[]User, error) { + return api.GetUsersInfoContext(context.Background(), users...) +} + +// GetUsersInfoContext will retrieve the complete multi-users information with a custom context. +// Slack API docs: https://api.slack.com/methods/users.info +func (api *Client) GetUsersInfoContext(ctx context.Context, users ...string) (*[]User, error) { + values := url.Values{ + "token": {api.token}, + "users": {strings.Join(users, ",")}, + "include_locale": {strconv.FormatBool(true)}, + } + + response, err := api.userRequest(ctx, "users.info", values) + if err != nil { + return nil, err + } + return &response.Users, nil +} + // GetUsersOption options for the GetUsers method call. type GetUsersOption func(*UserPagination) @@ -269,6 +308,13 @@ func GetUsersOptionPresence(n bool) GetUsersOption { } } +// GetUsersOptionTeamID include team Id +func GetUsersOptionTeamID(teamId string) GetUsersOption { + return func(p *UserPagination) { + p.teamId = teamId + } +} + func newUserPagination(c *Client, options ...GetUsersOption) (up UserPagination) { up = UserPagination{ c: c, @@ -287,6 +333,7 @@ type UserPagination struct { Users []User limit int presence bool + teamId string previousResp *ResponseMetadata c *Client } @@ -321,6 +368,7 @@ func (t UserPagination) Next(ctx context.Context) (_ UserPagination, err error) "presence": {strconv.FormatBool(t.presence)}, "token": {t.c.token}, "cursor": {t.previousResp.Cursor}, + "team_id": {t.teamId}, "include_locale": {strconv.FormatBool(true)}, } @@ -341,13 +389,13 @@ func (api *Client) GetUsersPaginated(options ...GetUsersOption) UserPagination { } // GetUsers returns the list of users (with their detailed information) -func (api *Client) GetUsers() ([]User, error) { - return api.GetUsersContext(context.Background()) +func (api *Client) GetUsers(options ...GetUsersOption) ([]User, error) { + return api.GetUsersContext(context.Background(), options...) } // GetUsersContext returns the list of users (with their detailed information) with a custom context -func (api *Client) GetUsersContext(ctx context.Context) (results []User, err error) { - p := api.GetUsersPaginated() +func (api *Client) GetUsersContext(ctx context.Context, options ...GetUsersOption) (results []User, err error) { + p := api.GetUsersPaginated(options...) for err == nil { p, err = p.Next(ctx) if err == nil { @@ -365,12 +413,14 @@ func (api *Client) GetUsersContext(ctx context.Context) (results []User, err err return results, p.Failure(err) } -// GetUserByEmail will retrieve the complete user information by email +// GetUserByEmail will retrieve the complete user information by email. +// For more information see the GetUserByEmailContext documentation. func (api *Client) GetUserByEmail(email string) (*User, error) { return api.GetUserByEmailContext(context.Background(), email) } -// GetUserByEmailContext will retrieve the complete user information by email with a custom context +// GetUserByEmailContext will retrieve the complete user information by email with a custom context. +// Slack API docs: https://api.slack.com/methods/users.lookupByEmail func (api *Client) GetUserByEmailContext(ctx context.Context, email string) (*User, error) { values := url.Values{ "token": {api.token}, @@ -383,12 +433,14 @@ func (api *Client) GetUserByEmailContext(ctx context.Context, email string) (*Us return &response.User, nil } -// SetUserAsActive marks the currently authenticated user as active +// SetUserAsActive marks the currently authenticated user as active. +// For more information see the SetUserAsActiveContext documentation. func (api *Client) SetUserAsActive() error { return api.SetUserAsActiveContext(context.Background()) } -// SetUserAsActiveContext marks the currently authenticated user as active with a custom context +// SetUserAsActiveContext marks the currently authenticated user as active with a custom context. +// Slack API docs: https://api.slack.com/methods/users.setActive func (api *Client) SetUserAsActiveContext(ctx context.Context) (err error) { values := url.Values{ "token": {api.token}, @@ -398,12 +450,14 @@ func (api *Client) SetUserAsActiveContext(ctx context.Context) (err error) { return err } -// SetUserPresence changes the currently authenticated user presence +// SetUserPresence changes the currently authenticated user presence. +// For more information see the SetUserPresenceContext documentation. func (api *Client) SetUserPresence(presence string) error { return api.SetUserPresenceContext(context.Background(), presence) } -// SetUserPresenceContext changes the currently authenticated user presence with a custom context +// SetUserPresenceContext changes the currently authenticated user presence with a custom context. +// Slack API docs: https://api.slack.com/methods/users.setPresence func (api *Client) SetUserPresenceContext(ctx context.Context, presence string) error { values := url.Values{ "token": {api.token}, @@ -414,12 +468,14 @@ func (api *Client) SetUserPresenceContext(ctx context.Context, presence string) return err } -// GetUserIdentity will retrieve user info available per identity scopes +// GetUserIdentity will retrieve user info available per identity scopes. +// For more information see the GetUserIdentityContext documentation. func (api *Client) GetUserIdentity() (*UserIdentityResponse, error) { return api.GetUserIdentityContext(context.Background()) } -// GetUserIdentityContext will retrieve user info available per identity scopes with a custom context +// GetUserIdentityContext will retrieve user info available per identity scopes with a custom context. +// Slack API docs: https://api.slack.com/methods/users.identity func (api *Client) GetUserIdentityContext(ctx context.Context) (response *UserIdentityResponse, err error) { values := url.Values{ "token": {api.token}, @@ -438,17 +494,17 @@ func (api *Client) GetUserIdentityContext(ctx context.Context) (response *UserId return response, nil } -// SetUserPhoto changes the currently authenticated user's profile image +// SetUserPhoto changes the currently authenticated user's profile image. +// For more information see the SetUserPhotoContext documentation. func (api *Client) SetUserPhoto(image string, params UserSetPhotoParams) error { return api.SetUserPhotoContext(context.Background(), image, params) } -// SetUserPhotoContext changes the currently authenticated user's profile image using a custom context +// SetUserPhotoContext changes the currently authenticated user's profile image using a custom context. +// Slack API docs: https://api.slack.com/methods/users.setPhoto func (api *Client) SetUserPhotoContext(ctx context.Context, image string, params UserSetPhotoParams) (err error) { response := &SlackResponse{} - values := url.Values{ - "token": {api.token}, - } + values := url.Values{} if params.CropX != DEFAULT_USER_PHOTO_CROP_X { values.Add("crop_x", strconv.Itoa(params.CropX)) } @@ -459,7 +515,7 @@ func (api *Client) SetUserPhotoContext(ctx context.Context, image string, params values.Add("crop_w", strconv.Itoa(params.CropW)) } - err = postLocalWithMultipartResponse(ctx, api.httpclient, api.endpoint+"users.setPhoto", image, "image", values, response, api) + err = postLocalWithMultipartResponse(ctx, api.httpclient, api.endpoint+"users.setPhoto", image, "image", api.token, values, response, api) if err != nil { return err } @@ -467,12 +523,14 @@ func (api *Client) SetUserPhotoContext(ctx context.Context, image string, params return response.Err() } -// DeleteUserPhoto deletes the current authenticated user's profile image +// DeleteUserPhoto deletes the current authenticated user's profile image. +// For more information see the DeleteUserPhotoContext documentation. func (api *Client) DeleteUserPhoto() error { return api.DeleteUserPhotoContext(context.Background()) } -// DeleteUserPhotoContext deletes the current authenticated user's profile image with a custom context +// DeleteUserPhotoContext deletes the current authenticated user's profile image with a custom context. +// Slack API docs: https://api.slack.com/methods/users.deletePhoto func (api *Client) DeleteUserPhotoContext(ctx context.Context) (err error) { response := &SlackResponse{} values := url.Values{ @@ -487,32 +545,120 @@ func (api *Client) DeleteUserPhotoContext(ctx context.Context) (err error) { return response.Err() } -// SetUserCustomStatus will set a custom status and emoji for the currently -// authenticated user. If statusEmoji is "" and statusText is not, the Slack API -// will automatically set it to ":speech_balloon:". Otherwise, if both are "" -// the Slack API will unset the custom status/emoji. If statusExpiration is set to 0 -// the status will not expire. +// SetUserRealName changes the currently authenticated user's realName +// For more information see the SetUserRealNameContextWithUser documentation. +func (api *Client) SetUserRealName(realName string) error { + return api.SetUserRealNameContextWithUser(context.Background(), "", realName) +} + +// SetUserRealNameContextWithUser will set a real name for the provided user with a custom context. +// Slack API docs: https://api.slack.com/methods/users.profile.set +func (api *Client) SetUserRealNameContextWithUser(ctx context.Context, user, realName string) error { + profile, err := json.Marshal( + &struct { + RealName string `json:"real_name"` + }{ + RealName: realName, + }, + ) + + if err != nil { + return err + } + + values := url.Values{ + "token": {api.token}, + "profile": {string(profile)}, + } + + // optional field. It should not be set if empty + if user != "" { + values["user"] = []string{user} + } + + response := &userResponseFull{} + if err = api.postMethod(ctx, "users.profile.set", values, response); err != nil { + return err + } + + return response.Err() +} + +// SetUserCustomFields sets Custom Profile fields on the provided users account. +// For more information see the SetUserCustomFieldsContext documentation. +func (api *Client) SetUserCustomFields(userID string, customFields map[string]UserProfileCustomField) error { + return api.SetUserCustomFieldsContext(context.Background(), userID, customFields) +} + +// SetUserCustomFieldsContext sets Custom Profile fields on the provided users account. +// Due to the non-repeating elements within the request, a map fields is required. +// The key in the map signifies the field that will be updated. +// +// Note: You may need to change the way the custom field is populated within the Profile section of the Admin Console +// from SCIM or User Entered to API. +// +// See GetTeamProfile for information to retrieve possible fields for your account. +// +// Slack API docs: https://api.slack.com/methods/users.profile.set +func (api *Client) SetUserCustomFieldsContext(ctx context.Context, userID string, customFields map[string]UserProfileCustomField) error { + + // Convert data to data type with custom marshall / unmarshall + // For more information, see UserProfileCustomFields definition. + updateFields := UserProfileCustomFields{} + updateFields.SetMap(customFields) + + // This anonymous struct is needed to set the fields level of the request data. The base struct for + // UserProfileCustomFields has an unexported variable named fields that does not contain a struct tag, + // which has resulted in this configuration. + profile, err := json.Marshal(&struct { + Fields UserProfileCustomFields `json:"fields"` + }{ + Fields: updateFields, + }) + + if err != nil { + return err + } + + values := url.Values{ + "token": {api.token}, + "user": {userID}, + "profile": {string(profile)}, + } + + response := &userResponseFull{} + if err := postForm(ctx, api.httpclient, APIURL+"users.profile.set", values, response, api); err != nil { + return err + } + + return response.Err() + +} + +// SetUserCustomStatus will set a custom status and emoji for the currently authenticated user. +// For more information see the SetUserCustomStatusContext documentation. func (api *Client) SetUserCustomStatus(statusText, statusEmoji string, statusExpiration int64) error { return api.SetUserCustomStatusContextWithUser(context.Background(), "", statusText, statusEmoji, statusExpiration) } -// SetUserCustomStatusContext will set a custom status and emoji for the currently authenticated user with a custom context -// -// For more information see SetUserCustomStatus +// SetUserCustomStatusContext will set a custom status and emoji for the currently authenticated user with a custom context. +// For more information see the SetUserCustomStatusContextWithUser documentation. func (api *Client) SetUserCustomStatusContext(ctx context.Context, statusText, statusEmoji string, statusExpiration int64) error { - return api.SetUserCustomStatusContextWithUser(context.Background(), "", statusText, statusEmoji, statusExpiration) + return api.SetUserCustomStatusContextWithUser(ctx, "", statusText, statusEmoji, statusExpiration) } // SetUserCustomStatusWithUser will set a custom status and emoji for the provided user. -// -// For more information see SetUserCustomStatus +// For more information see the SetUserCustomStatusContextWithUser documentation. func (api *Client) SetUserCustomStatusWithUser(user, statusText, statusEmoji string, statusExpiration int64) error { return api.SetUserCustomStatusContextWithUser(context.Background(), user, statusText, statusEmoji, statusExpiration) } -// SetUserCustomStatusContextWithUser will set a custom status and emoji for the provided user with a custom context +// SetUserCustomStatusContextWithUser will set a custom status and emoji for the currently authenticated user. +// If statusEmoji is "" and statusText is not, the Slack API will automatically set it to ":speech_balloon:". +// Otherwise, if both are "" the Slack API will unset the custom status/emoji. If statusExpiration is set to 0 +// the status will not expire. // -// For more information see SetUserCustomStatus +// Slack API docs: https://api.slack.com/methods/users.profile.set func (api *Client) SetUserCustomStatusContextWithUser(ctx context.Context, user, statusText, statusEmoji string, statusExpiration int64) error { // XXX(theckman): this anonymous struct is for making requests to the Slack // API for setting and unsetting a User's Custom Status/Emoji. To change @@ -541,11 +687,15 @@ func (api *Client) SetUserCustomStatusContextWithUser(ctx context.Context, user, } values := url.Values{ - "user": {user}, "token": {api.token}, "profile": {string(profile)}, } + // optional field. It should not be set if empty + if user != "" { + values["user"] = []string{user} + } + response := &userResponseFull{} if err = api.postMethod(ctx, "users.profile.set", values, response); err != nil { return err @@ -566,9 +716,16 @@ func (api *Client) UnsetUserCustomStatusContext(ctx context.Context) error { return api.SetUserCustomStatusContext(ctx, "", "", 0) } +// GetUserProfileParameters are the parameters required to get user profile +type GetUserProfileParameters struct { + UserID string + IncludeLabels bool +} + // GetUserProfile retrieves a user's profile information. -func (api *Client) GetUserProfile(userID string, includeLabels bool) (*UserProfile, error) { - return api.GetUserProfileContext(context.Background(), userID, includeLabels) +// For more information see the GetUserProfileContext documentation. +func (api *Client) GetUserProfile(params *GetUserProfileParameters) (*UserProfile, error) { + return api.GetUserProfileContext(context.Background(), params) } type getUserProfileResponse struct { @@ -577,9 +734,14 @@ type getUserProfileResponse struct { } // GetUserProfileContext retrieves a user's profile information with a context. -func (api *Client) GetUserProfileContext(ctx context.Context, userID string, includeLabels bool) (*UserProfile, error) { - values := url.Values{"token": {api.token}, "user": {userID}} - if includeLabels { +// Slack API docs: https://api.slack.com/methods/users.profile.get +func (api *Client) GetUserProfileContext(ctx context.Context, params *GetUserProfileParameters) (*UserProfile, error) { + values := url.Values{"token": {api.token}} + + if params.UserID != "" { + values.Add("user", params.UserID) + } + if params.IncludeLabels { values.Add("include_labels", "true") } resp := &getUserProfileResponse{} diff --git a/vendor/github.com/slack-go/slack/views.go b/vendor/github.com/slack-go/slack/views.go index c34feec..0822d2c 100644 --- a/vendor/github.com/slack-go/slack/views.go +++ b/vendor/github.com/slack-go/slack/views.go @@ -18,28 +18,37 @@ type ViewState struct { type View struct { SlackResponse - ID string `json:"id"` - TeamID string `json:"team_id"` - Type ViewType `json:"type"` - Title *TextBlockObject `json:"title"` - Close *TextBlockObject `json:"close"` - Submit *TextBlockObject `json:"submit"` - Blocks Blocks `json:"blocks"` - PrivateMetadata string `json:"private_metadata"` - CallbackID string `json:"callback_id"` - State *ViewState `json:"state"` - Hash string `json:"hash"` - ClearOnClose bool `json:"clear_on_close"` - NotifyOnClose bool `json:"notify_on_close"` - RootViewID string `json:"root_view_id"` - PreviousViewID string `json:"previous_view_id"` - AppID string `json:"app_id"` - ExternalID string `json:"external_id"` - BotID string `json:"bot_id"` + ID string `json:"id"` + TeamID string `json:"team_id"` + Type ViewType `json:"type"` + Title *TextBlockObject `json:"title"` + Close *TextBlockObject `json:"close"` + Submit *TextBlockObject `json:"submit"` + Blocks Blocks `json:"blocks"` + PrivateMetadata string `json:"private_metadata"` + CallbackID string `json:"callback_id"` + State *ViewState `json:"state"` + Hash string `json:"hash"` + ClearOnClose bool `json:"clear_on_close"` + NotifyOnClose bool `json:"notify_on_close"` + RootViewID string `json:"root_view_id"` + PreviousViewID string `json:"previous_view_id"` + AppID string `json:"app_id"` + ExternalID string `json:"external_id"` + BotID string `json:"bot_id"` + AppInstalledTeamID string `json:"app_installed_team_id"` +} + +type ViewSubmissionCallbackResponseURL struct { + BlockID string `json:"block_id"` + ActionID string `json:"action_id"` + ChannelID string `json:"channel_id"` + ResponseURL string `json:"response_url"` } type ViewSubmissionCallback struct { - Hash string `json:"hash"` + Hash string `json:"hash"` + ResponseURLs []ViewSubmissionCallbackResponseURL `json:"response_urls,omitempty"` } type ViewClosedCallback struct { @@ -90,7 +99,7 @@ func NewErrorsViewSubmissionResponse(errors map[string]string) *ViewSubmissionRe type ModalViewRequest struct { Type ViewType `json:"type"` - Title *TextBlockObject `json:"title"` + Title *TextBlockObject `json:"title,omitempty"` Blocks Blocks `json:"blocks"` Close *TextBlockObject `json:"close,omitempty"` Submit *TextBlockObject `json:"submit,omitempty"` @@ -146,11 +155,30 @@ type ViewResponse struct { } // OpenView opens a view for a user. +// For more information see the OpenViewContext documentation. func (api *Client) OpenView(triggerID string, view ModalViewRequest) (*ViewResponse, error) { return api.OpenViewContext(context.Background(), triggerID, view) } +// ValidateUniqueBlockID will verify if each input block has a unique block ID if set +func ValidateUniqueBlockID(view ModalViewRequest) bool { + + uniqueBlockID := map[string]bool{} + + for _, b := range view.Blocks.BlockSet { + if inputBlock, ok := b.(*InputBlock); ok { + if _, ok := uniqueBlockID[inputBlock.BlockID]; ok { + return false + } + uniqueBlockID[inputBlock.BlockID] = true + } + } + + return true +} + // OpenViewContext opens a view for a user with a custom context. +// Slack API docs: https://api.slack.com/methods/views.open func (api *Client) OpenViewContext( ctx context.Context, triggerID string, @@ -159,6 +187,11 @@ func (api *Client) OpenViewContext( if triggerID == "" { return nil, ErrParametersMissing } + + if !ValidateUniqueBlockID(view) { + return nil, ErrBlockIDNotUnique + } + req := openViewRequest{ TriggerID: triggerID, View: view, @@ -177,11 +210,13 @@ func (api *Client) OpenViewContext( } // PublishView publishes a static view for a user. +// For more information see the PublishViewContext documentation. func (api *Client) PublishView(userID string, view HomeTabViewRequest, hash string) (*ViewResponse, error) { return api.PublishViewContext(context.Background(), userID, view, hash) } // PublishViewContext publishes a static view for a user with a custom context. +// Slack API docs: https://api.slack.com/methods/views.publish func (api *Client) PublishViewContext( ctx context.Context, userID string, @@ -210,11 +245,13 @@ func (api *Client) PublishViewContext( } // PushView pushes a view onto the stack of a root view. +// For more information see the PushViewContext documentation. func (api *Client) PushView(triggerID string, view ModalViewRequest) (*ViewResponse, error) { return api.PushViewContext(context.Background(), triggerID, view) } -// PublishViewContext pushes a view onto the stack of a root view with a custom context. +// PushViewContext pushes a view onto the stack of a root view with a custom context. +// Slack API docs: https://api.slack.com/methods/views.push func (api *Client) PushViewContext( ctx context.Context, triggerID string, @@ -241,11 +278,13 @@ func (api *Client) PushViewContext( } // UpdateView updates an existing view. +// For more information see the UpdateViewContext documentation. func (api *Client) UpdateView(view ModalViewRequest, externalID, hash, viewID string) (*ViewResponse, error) { return api.UpdateViewContext(context.Background(), view, externalID, hash, viewID) } // UpdateViewContext updates an existing view with a custom context. +// Slack API docs: https://api.slack.com/methods/views.update func (api *Client) UpdateViewContext( ctx context.Context, view ModalViewRequest, diff --git a/vendor/github.com/slack-go/slack/webhooks.go b/vendor/github.com/slack-go/slack/webhooks.go index 1016cb4..5a854f3 100644 --- a/vendor/github.com/slack-go/slack/webhooks.go +++ b/vendor/github.com/slack-go/slack/webhooks.go @@ -1,7 +1,11 @@ package slack import ( + "bytes" "context" + "encoding/json" + "fmt" + "io" "net/http" ) @@ -14,6 +18,13 @@ type WebhookMessage struct { Text string `json:"text,omitempty"` Attachments []Attachment `json:"attachments,omitempty"` Parse string `json:"parse,omitempty"` + Blocks *Blocks `json:"blocks,omitempty"` + ResponseType string `json:"response_type,omitempty"` + ReplaceOriginal bool `json:"replace_original"` + DeleteOriginal bool `json:"delete_original"` + ReplyBroadcast bool `json:"reply_broadcast,omitempty"` + UnfurlLinks bool `json:"unfurl_links,omitempty"` + UnfurlMedia bool `json:"unfurl_media,omitempty"` } func PostWebhook(url string, msg *WebhookMessage) error { @@ -27,3 +38,27 @@ func PostWebhookContext(ctx context.Context, url string, msg *WebhookMessage) er func PostWebhookCustomHTTP(url string, httpClient *http.Client, msg *WebhookMessage) error { return PostWebhookCustomHTTPContext(context.Background(), url, httpClient, msg) } + +func PostWebhookCustomHTTPContext(ctx context.Context, url string, httpClient *http.Client, msg *WebhookMessage) error { + raw, err := json.Marshal(msg) + if err != nil { + return fmt.Errorf("marshal failed: %w", err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(raw)) + if err != nil { + return fmt.Errorf("failed new request: %w", err) + } + req.Header.Set("Content-Type", "application/json") + + resp, err := httpClient.Do(req) + if err != nil { + return fmt.Errorf("failed to post webhook: %w", err) + } + defer func() { + io.Copy(io.Discard, resp.Body) + resp.Body.Close() + }() + + return checkStatusCode(resp, discard{}) +} diff --git a/vendor/github.com/slack-go/slack/webhooks_go112.go b/vendor/github.com/slack-go/slack/webhooks_go112.go deleted file mode 100644 index 4e0db0e..0000000 --- a/vendor/github.com/slack-go/slack/webhooks_go112.go +++ /dev/null @@ -1,34 +0,0 @@ -// +build !go1.13 - -package slack - -import ( - "bytes" - "context" - "encoding/json" - "net/http" - - "github.com/pkg/errors" -) - -func PostWebhookCustomHTTPContext(ctx context.Context, url string, httpClient *http.Client, msg *WebhookMessage) error { - raw, err := json.Marshal(msg) - if err != nil { - return errors.Wrap(err, "marshal failed") - } - - req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(raw)) - if err != nil { - return errors.Wrap(err, "failed new request") - } - req = req.WithContext(ctx) - req.Header.Set("Content-Type", "application/json") - - resp, err := httpClient.Do(req) - if err != nil { - return errors.Wrap(err, "failed to post webhook") - } - defer resp.Body.Close() - - return checkStatusCode(resp, discard{}) -} diff --git a/vendor/github.com/slack-go/slack/webhooks_go113.go b/vendor/github.com/slack-go/slack/webhooks_go113.go deleted file mode 100644 index 99c243f..0000000 --- a/vendor/github.com/slack-go/slack/webhooks_go113.go +++ /dev/null @@ -1,33 +0,0 @@ -// +build go1.13 - -package slack - -import ( - "bytes" - "context" - "encoding/json" - "net/http" - - "github.com/pkg/errors" -) - -func PostWebhookCustomHTTPContext(ctx context.Context, url string, httpClient *http.Client, msg *WebhookMessage) error { - raw, err := json.Marshal(msg) - if err != nil { - return errors.Wrap(err, "marshal failed") - } - - req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(raw)) - if err != nil { - return errors.Wrap(err, "failed new request") - } - req.Header.Set("Content-Type", "application/json") - - resp, err := httpClient.Do(req) - if err != nil { - return errors.Wrap(err, "failed to post webhook") - } - defer resp.Body.Close() - - return checkStatusCode(resp, discard{}) -} diff --git a/vendor/github.com/slack-go/slack/websocket_internals.go b/vendor/github.com/slack-go/slack/websocket_internals.go index 3e1906e..454a213 100644 --- a/vendor/github.com/slack-go/slack/websocket_internals.go +++ b/vendor/github.com/slack-go/slack/websocket_internals.go @@ -94,6 +94,7 @@ func (i *IncomingEventError) Error() string { // AckErrorEvent i type AckErrorEvent struct { ErrorObj error + ReplyTo int } func (a *AckErrorEvent) Error() string { diff --git a/vendor/github.com/slack-go/slack/websocket_managed_conn.go b/vendor/github.com/slack-go/slack/websocket_managed_conn.go index a8844f8..f107b2a 100644 --- a/vendor/github.com/slack-go/slack/websocket_managed_conn.go +++ b/vendor/github.com/slack-go/slack/websocket_managed_conn.go @@ -10,10 +10,37 @@ import ( "time" "github.com/gorilla/websocket" + + "github.com/slack-go/slack/internal/backoff" "github.com/slack-go/slack/internal/errorsx" "github.com/slack-go/slack/internal/timex" ) +// UnmappedError represents error occurred when there is no mapping between given event name +// and corresponding Go struct. +type UnmappedError struct { + // EventType returns event type name. + EventType string + // RawEvent returns raw event body. + RawEvent json.RawMessage + + ctxMsg string +} + +// NewUnmappedError returns new UnmappedError instance. +func NewUnmappedError(ctxMsg, eventType string, raw json.RawMessage) *UnmappedError { + return &UnmappedError{ + ctxMsg: ctxMsg, + EventType: eventType, + RawEvent: raw, + } +} + +// Error returns human-readable error message. +func (u UnmappedError) Error() string { + return fmt.Sprintf("%s: Received unmapped event %q", u.ctxMsg, u.EventType) +} + // ManageConnection can be called on a Slack RTM instance returned by the // NewRTM method. It will connect to the slack RTM API and handle all incoming // and outgoing events. If a connection fails then it will attempt to reconnect @@ -88,11 +115,12 @@ func (rtm *RTM) connect(connectionCount int, useRTMStart bool) (*Info, *websocke errInvalidAuth = "invalid_auth" errInactiveAccount = "account_inactive" errMissingAuthToken = "not_authed" + errTokenRevoked = "token_revoked" ) // used to provide exponential backoff wait time with jitter before trying // to connect to slack again - boff := &backoff{ + boff := &backoff.Backoff{ Max: 5 * time.Minute, } @@ -103,7 +131,7 @@ func (rtm *RTM) connect(connectionCount int, useRTMStart bool) (*Info, *websocke // send connecting event rtm.IncomingEvents <- RTMEvent{"connecting", &ConnectingEvent{ - Attempt: boff.attempts + 1, + Attempt: boff.Attempts() + 1, ConnectionCount: connectionCount, }} @@ -115,7 +143,7 @@ func (rtm *RTM) connect(connectionCount int, useRTMStart bool) (*Info, *websocke // check for fatal errors switch err.Error() { - case errInvalidAuth, errInactiveAccount, errMissingAuthToken: + case errInvalidAuth, errInactiveAccount, errMissingAuthToken, errTokenRevoked: rtm.Debugf("invalid auth when connecting with RTM: %s", err) rtm.IncomingEvents <- RTMEvent{"invalid_auth", &InvalidAuthEvent{}} return nil, nil, err @@ -123,7 +151,7 @@ func (rtm *RTM) connect(connectionCount int, useRTMStart bool) (*Info, *websocke } switch actual := err.(type) { - case statusCodeError: + case StatusCodeError: if actual.Code == http.StatusNotFound { rtm.Debugf("invalid auth when connecting with RTM: %s", err) rtm.IncomingEvents <- RTMEvent{"invalid_auth", &InvalidAuthEvent{}} @@ -138,13 +166,13 @@ func (rtm *RTM) connect(connectionCount int, useRTMStart bool) (*Info, *websocke // any other errors are treated as recoverable and we try again after // sending the event along the IncomingEvents channel rtm.IncomingEvents <- RTMEvent{"connection_error", &ConnectionErrorEvent{ - Attempt: boff.attempts, + Attempt: boff.Attempts(), Backoff: backoff, ErrorObj: err, }} // get time we should wait before attempting to connect again - rtm.Debugf("reconnection %d failed: %s reconnecting in %v\n", boff.attempts, err, backoff) + rtm.Debugf("reconnection %d failed: %s reconnecting in %v\n", boff.Attempts(), err, backoff) // wait for one of the following to occur, // backoff duration has elapsed, killChannel is signalled, or @@ -435,10 +463,10 @@ func (rtm *RTM) handleAck(event json.RawMessage) { if ack.RTMResponse.Error.Code == -1 && ack.RTMResponse.Error.Msg == "slow down, too many messages..." { rtm.IncomingEvents <- RTMEvent{"ack_error", &RateLimitEvent{}} } else { - rtm.IncomingEvents <- RTMEvent{"ack_error", &AckErrorEvent{ack.Error}} + rtm.IncomingEvents <- RTMEvent{"ack_error", &AckErrorEvent{ack.Error, ack.ReplyTo}} } } else { - rtm.IncomingEvents <- RTMEvent{"ack_error", &AckErrorEvent{fmt.Errorf("ack decode failure")}} + rtm.IncomingEvents <- RTMEvent{"ack_error", &AckErrorEvent{ErrorObj: fmt.Errorf("ack decode failure")}} } } @@ -471,7 +499,7 @@ func (rtm *RTM) handleEvent(typeStr string, event json.RawMessage) { v, exists := EventMapping[typeStr] if !exists { rtm.Debugf("RTM Error - received unmapped event %q: %s\n", typeStr, string(event)) - err := fmt.Errorf("RTM Error: Received unmapped event %q: %s", typeStr, string(event)) + err := NewUnmappedError("RTM Error", typeStr, event) rtm.IncomingEvents <- RTMEvent{"unmarshalling_error", &UnmarshallingErrorEvent{err}} return } @@ -480,7 +508,7 @@ func (rtm *RTM) handleEvent(typeStr string, event json.RawMessage) { err := json.Unmarshal(event, recvEvent) if err != nil { rtm.Debugf("RTM Error, could not unmarshall event %q: %s\n", typeStr, string(event)) - err := fmt.Errorf("RTM Error: Could not unmarshall event %q: %s", typeStr, string(event)) + err := fmt.Errorf("RTM Error: Could not unmarshall event %q", typeStr) rtm.IncomingEvents <- RTMEvent{"unmarshalling_error", &UnmarshallingErrorEvent{err}} return } @@ -572,10 +600,11 @@ var EventMapping = map[string]interface{}{ "member_joined_channel": MemberJoinedChannelEvent{}, "member_left_channel": MemberLeftChannelEvent{}, - "subteam_created": SubteamCreatedEvent{}, - "subteam_self_added": SubteamSelfAddedEvent{}, - "subteam_self_removed": SubteamSelfRemovedEvent{}, - "subteam_updated": SubteamUpdatedEvent{}, + "subteam_created": SubteamCreatedEvent{}, + "subteam_members_changed": SubteamMembersChangedEvent{}, + "subteam_self_added": SubteamSelfAddedEvent{}, + "subteam_self_removed": SubteamSelfRemovedEvent{}, + "subteam_updated": SubteamUpdatedEvent{}, "desktop_notification": DesktopNotificationEvent{}, "mobile_in_app_notification": MobileInAppNotificationEvent{}, diff --git a/vendor/github.com/slack-go/slack/websocket_reactions.go b/vendor/github.com/slack-go/slack/websocket_reactions.go index e497387..6098a6c 100644 --- a/vendor/github.com/slack-go/slack/websocket_reactions.go +++ b/vendor/github.com/slack-go/slack/websocket_reactions.go @@ -1,7 +1,7 @@ package slack -// reactionItem is a lighter-weight item than is returned by the reactions list. -type reactionItem struct { +// ReactionItem is a lighter-weight item than is returned by the reactions list. +type ReactionItem struct { Type string `json:"type"` Channel string `json:"channel,omitempty"` File string `json:"file,omitempty"` @@ -9,17 +9,17 @@ type reactionItem struct { Timestamp string `json:"ts,omitempty"` } -type reactionEvent struct { +type ReactionEvent struct { Type string `json:"type"` User string `json:"user"` ItemUser string `json:"item_user"` - Item reactionItem `json:"item"` + Item ReactionItem `json:"item"` Reaction string `json:"reaction"` EventTimestamp string `json:"event_ts"` } // ReactionAddedEvent represents the Reaction added event -type ReactionAddedEvent reactionEvent +type ReactionAddedEvent ReactionEvent // ReactionRemovedEvent represents the Reaction removed event -type ReactionRemovedEvent reactionEvent +type ReactionRemovedEvent ReactionEvent diff --git a/vendor/github.com/slack-go/slack/websocket_subteam.go b/vendor/github.com/slack-go/slack/websocket_subteam.go index a23b274..2f61873 100644 --- a/vendor/github.com/slack-go/slack/websocket_subteam.go +++ b/vendor/github.com/slack-go/slack/websocket_subteam.go @@ -14,9 +14,9 @@ type SubteamMembersChangedEvent struct { DatePreviousUpdate JSONTime `json:"date_previous_update"` DateUpdate JSONTime `json:"date_update"` AddedUsers []string `json:"added_users"` - AddedUsersCount string `json:"added_users_count"` + AddedUsersCount int `json:"added_users_count"` RemovedUsers []string `json:"removed_users"` - RemovedUsersCount string `json:"removed_users_count"` + RemovedUsersCount int `json:"removed_users_count"` } // SubteamSelfAddedEvent represents an event of you have been added to a User Group diff --git a/vendor/github.com/slack-go/slack/workflow_step.go b/vendor/github.com/slack-go/slack/workflow_step.go new file mode 100644 index 0000000..747a5dc --- /dev/null +++ b/vendor/github.com/slack-go/slack/workflow_step.go @@ -0,0 +1,101 @@ +package slack + +import ( + "context" + "encoding/json" +) + +const VTWorkflowStep ViewType = "workflow_step" + +type ( + ConfigurationModalRequest struct { + ModalViewRequest + } + + WorkflowStepCompleteResponse struct { + WorkflowStepEditID string `json:"workflow_step_edit_id"` + Inputs *WorkflowStepInputs `json:"inputs,omitempty"` + Outputs *[]WorkflowStepOutput `json:"outputs,omitempty"` + } + + WorkflowStepInputElement struct { + Value string `json:"value"` + SkipVariableReplacement bool `json:"skip_variable_replacement"` + } + + WorkflowStepInputs map[string]WorkflowStepInputElement + + WorkflowStepOutput struct { + Name string `json:"name"` + Type string `json:"type"` + Label string `json:"label"` + } +) + +func NewConfigurationModalRequest(blocks Blocks, privateMetaData string, externalID string) *ConfigurationModalRequest { + return &ConfigurationModalRequest{ + ModalViewRequest{ + Type: VTWorkflowStep, + Title: nil, // slack configuration modal must not have a title! + Blocks: blocks, + PrivateMetadata: privateMetaData, + ExternalID: externalID, + }, + } +} + +// SaveWorkflowStepConfiguration opens a configuration modal for a workflow step. +// For more information see the SaveWorkflowStepConfigurationContext documentation. +func (api *Client) SaveWorkflowStepConfiguration(workflowStepEditID string, inputs *WorkflowStepInputs, outputs *[]WorkflowStepOutput) error { + return api.SaveWorkflowStepConfigurationContext(context.Background(), workflowStepEditID, inputs, outputs) +} + +// SaveWorkflowStepConfigurationContext saves the configuration of a workflow step with a custom context. +// Slack API docs: https://api.slack.com/methods/workflows.updateStep +func (api *Client) SaveWorkflowStepConfigurationContext(ctx context.Context, workflowStepEditID string, inputs *WorkflowStepInputs, outputs *[]WorkflowStepOutput) error { + wscr := WorkflowStepCompleteResponse{ + WorkflowStepEditID: workflowStepEditID, + Inputs: inputs, + Outputs: outputs, + } + + endpoint := api.endpoint + "workflows.updateStep" + jsonData, err := json.Marshal(wscr) + if err != nil { + return err + } + + response := &SlackResponse{} + if err := postJSON(ctx, api.httpclient, endpoint, api.token, jsonData, response, api); err != nil { + return err + } + + if !response.Ok { + return response.Err() + } + + return nil +} + +func GetInitialOptionFromWorkflowStepInput(selection *SelectBlockElement, inputs *WorkflowStepInputs, options []*OptionBlockObject) (*OptionBlockObject, bool) { + if len(*inputs) == 0 { + return &OptionBlockObject{}, false + } + if len(options) == 0 { + return &OptionBlockObject{}, false + } + + if val, ok := (*inputs)[selection.ActionID]; ok { + if val.SkipVariableReplacement { + return &OptionBlockObject{}, false + } + + for _, option := range options { + if option.Value == val.Value { + return option, true + } + } + } + + return &OptionBlockObject{}, false +} diff --git a/vendor/github.com/slack-go/slack/workflow_step_execute.go b/vendor/github.com/slack-go/slack/workflow_step_execute.go new file mode 100644 index 0000000..32db9ba --- /dev/null +++ b/vendor/github.com/slack-go/slack/workflow_step_execute.go @@ -0,0 +1,85 @@ +package slack + +import ( + "context" + "encoding/json" +) + +type ( + WorkflowStepCompletedRequest struct { + WorkflowStepExecuteID string `json:"workflow_step_execute_id"` + Outputs map[string]string `json:"outputs"` + } + + WorkflowStepFailedRequest struct { + WorkflowStepExecuteID string `json:"workflow_step_execute_id"` + Error struct { + Message string `json:"message"` + } `json:"error"` + } +) + +type WorkflowStepCompletedRequestOption func(opt *WorkflowStepCompletedRequest) error + +func WorkflowStepCompletedRequestOptionOutput(outputs map[string]string) WorkflowStepCompletedRequestOption { + return func(opt *WorkflowStepCompletedRequest) error { + if len(outputs) > 0 { + opt.Outputs = outputs + } + return nil + } +} + +// WorkflowStepCompleted indicates step is completed +func (api *Client) WorkflowStepCompleted(workflowStepExecuteID string, options ...WorkflowStepCompletedRequestOption) error { + // More information: https://api.slack.com/methods/workflows.stepCompleted + r := &WorkflowStepCompletedRequest{ + WorkflowStepExecuteID: workflowStepExecuteID, + } + for _, option := range options { + option(r) + } + + endpoint := api.endpoint + "workflows.stepCompleted" + jsonData, err := json.Marshal(r) + if err != nil { + return err + } + + response := &SlackResponse{} + if err := postJSON(context.Background(), api.httpclient, endpoint, api.token, jsonData, response, api); err != nil { + return err + } + + if !response.Ok { + return response.Err() + } + + return nil +} + +// WorkflowStepFailed indicates step is failed +func (api *Client) WorkflowStepFailed(workflowStepExecuteID string, errorMessage string) error { + // More information: https://api.slack.com/methods/workflows.stepFailed + r := WorkflowStepFailedRequest{ + WorkflowStepExecuteID: workflowStepExecuteID, + } + r.Error.Message = errorMessage + + endpoint := api.endpoint + "workflows.stepFailed" + jsonData, err := json.Marshal(r) + if err != nil { + return err + } + + response := &SlackResponse{} + if err := postJSON(context.Background(), api.httpclient, endpoint, api.token, jsonData, response, api); err != nil { + return err + } + + if !response.Ok { + return response.Err() + } + + return nil +} diff --git a/vendor/golang.org/x/xerrors/LICENSE b/vendor/golang.org/x/xerrors/LICENSE deleted file mode 100644 index e4a47e1..0000000 --- a/vendor/golang.org/x/xerrors/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2019 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/xerrors/PATENTS b/vendor/golang.org/x/xerrors/PATENTS deleted file mode 100644 index 7330990..0000000 --- a/vendor/golang.org/x/xerrors/PATENTS +++ /dev/null @@ -1,22 +0,0 @@ -Additional IP Rights Grant (Patents) - -"This implementation" means the copyrightable works distributed by -Google as part of the Go project. - -Google 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, -transfer and otherwise run, modify and propagate the contents of this -implementation of Go, where such license applies only to those patent -claims, both currently owned or controlled by Google and acquired in -the future, licensable by Google that are necessarily infringed by this -implementation of Go. This grant does not include claims that would be -infringed only as a consequence of further modification of this -implementation. If you or your agent or exclusive licensee institute or -order or agree to the institution of patent litigation against any -entity (including a cross-claim or counterclaim in a lawsuit) alleging -that this implementation of Go or any code incorporated within this -implementation of Go constitutes direct or contributory patent -infringement, or inducement of patent infringement, then any patent -rights granted to you under this License for this implementation of Go -shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/xerrors/README b/vendor/golang.org/x/xerrors/README deleted file mode 100644 index aac7867..0000000 --- a/vendor/golang.org/x/xerrors/README +++ /dev/null @@ -1,2 +0,0 @@ -This repository holds the transition packages for the new Go 1.13 error values. -See golang.org/design/29934-error-values. diff --git a/vendor/golang.org/x/xerrors/adaptor.go b/vendor/golang.org/x/xerrors/adaptor.go deleted file mode 100644 index 4317f24..0000000 --- a/vendor/golang.org/x/xerrors/adaptor.go +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package xerrors - -import ( - "bytes" - "fmt" - "io" - "reflect" - "strconv" -) - -// FormatError calls the FormatError method of f with an errors.Printer -// configured according to s and verb, and writes the result to s. -func FormatError(f Formatter, s fmt.State, verb rune) { - // Assuming this function is only called from the Format method, and given - // that FormatError takes precedence over Format, it cannot be called from - // any package that supports errors.Formatter. It is therefore safe to - // disregard that State may be a specific printer implementation and use one - // of our choice instead. - - // limitations: does not support printing error as Go struct. - - var ( - sep = " " // separator before next error - p = &state{State: s} - direct = true - ) - - var err error = f - - switch verb { - // Note that this switch must match the preference order - // for ordinary string printing (%#v before %+v, and so on). - - case 'v': - if s.Flag('#') { - if stringer, ok := err.(fmt.GoStringer); ok { - io.WriteString(&p.buf, stringer.GoString()) - goto exit - } - // proceed as if it were %v - } else if s.Flag('+') { - p.printDetail = true - sep = "\n - " - } - case 's': - case 'q', 'x', 'X': - // Use an intermediate buffer in the rare cases that precision, - // truncation, or one of the alternative verbs (q, x, and X) are - // specified. - direct = false - - default: - p.buf.WriteString("%!") - p.buf.WriteRune(verb) - p.buf.WriteByte('(') - switch { - case err != nil: - p.buf.WriteString(reflect.TypeOf(f).String()) - default: - p.buf.WriteString("") - } - p.buf.WriteByte(')') - io.Copy(s, &p.buf) - return - } - -loop: - for { - switch v := err.(type) { - case Formatter: - err = v.FormatError((*printer)(p)) - case fmt.Formatter: - v.Format(p, 'v') - break loop - default: - io.WriteString(&p.buf, v.Error()) - break loop - } - if err == nil { - break - } - if p.needColon || !p.printDetail { - p.buf.WriteByte(':') - p.needColon = false - } - p.buf.WriteString(sep) - p.inDetail = false - p.needNewline = false - } - -exit: - width, okW := s.Width() - prec, okP := s.Precision() - - if !direct || (okW && width > 0) || okP { - // Construct format string from State s. - format := []byte{'%'} - if s.Flag('-') { - format = append(format, '-') - } - if s.Flag('+') { - format = append(format, '+') - } - if s.Flag(' ') { - format = append(format, ' ') - } - if okW { - format = strconv.AppendInt(format, int64(width), 10) - } - if okP { - format = append(format, '.') - format = strconv.AppendInt(format, int64(prec), 10) - } - format = append(format, string(verb)...) - fmt.Fprintf(s, string(format), p.buf.String()) - } else { - io.Copy(s, &p.buf) - } -} - -var detailSep = []byte("\n ") - -// state tracks error printing state. It implements fmt.State. -type state struct { - fmt.State - buf bytes.Buffer - - printDetail bool - inDetail bool - needColon bool - needNewline bool -} - -func (s *state) Write(b []byte) (n int, err error) { - if s.printDetail { - if len(b) == 0 { - return 0, nil - } - if s.inDetail && s.needColon { - s.needNewline = true - if b[0] == '\n' { - b = b[1:] - } - } - k := 0 - for i, c := range b { - if s.needNewline { - if s.inDetail && s.needColon { - s.buf.WriteByte(':') - s.needColon = false - } - s.buf.Write(detailSep) - s.needNewline = false - } - if c == '\n' { - s.buf.Write(b[k:i]) - k = i + 1 - s.needNewline = true - } - } - s.buf.Write(b[k:]) - if !s.inDetail { - s.needColon = true - } - } else if !s.inDetail { - s.buf.Write(b) - } - return len(b), nil -} - -// printer wraps a state to implement an xerrors.Printer. -type printer state - -func (s *printer) Print(args ...interface{}) { - if !s.inDetail || s.printDetail { - fmt.Fprint((*state)(s), args...) - } -} - -func (s *printer) Printf(format string, args ...interface{}) { - if !s.inDetail || s.printDetail { - fmt.Fprintf((*state)(s), format, args...) - } -} - -func (s *printer) Detail() bool { - s.inDetail = true - return s.printDetail -} diff --git a/vendor/golang.org/x/xerrors/codereview.cfg b/vendor/golang.org/x/xerrors/codereview.cfg deleted file mode 100644 index 3f8b14b..0000000 --- a/vendor/golang.org/x/xerrors/codereview.cfg +++ /dev/null @@ -1 +0,0 @@ -issuerepo: golang/go diff --git a/vendor/golang.org/x/xerrors/doc.go b/vendor/golang.org/x/xerrors/doc.go deleted file mode 100644 index eef99d9..0000000 --- a/vendor/golang.org/x/xerrors/doc.go +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package xerrors implements functions to manipulate errors. -// -// This package is based on the Go 2 proposal for error values: -// https://golang.org/design/29934-error-values -// -// These functions were incorporated into the standard library's errors package -// in Go 1.13: -// - Is -// - As -// - Unwrap -// -// Also, Errorf's %w verb was incorporated into fmt.Errorf. -// -// Use this package to get equivalent behavior in all supported Go versions. -// -// No other features of this package were included in Go 1.13, and at present -// there are no plans to include any of them. -package xerrors // import "golang.org/x/xerrors" diff --git a/vendor/golang.org/x/xerrors/errors.go b/vendor/golang.org/x/xerrors/errors.go deleted file mode 100644 index e88d377..0000000 --- a/vendor/golang.org/x/xerrors/errors.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package xerrors - -import "fmt" - -// errorString is a trivial implementation of error. -type errorString struct { - s string - frame Frame -} - -// New returns an error that formats as the given text. -// -// The returned error contains a Frame set to the caller's location and -// implements Formatter to show this information when printed with details. -func New(text string) error { - return &errorString{text, Caller(1)} -} - -func (e *errorString) Error() string { - return e.s -} - -func (e *errorString) Format(s fmt.State, v rune) { FormatError(e, s, v) } - -func (e *errorString) FormatError(p Printer) (next error) { - p.Print(e.s) - e.frame.Format(p) - return nil -} diff --git a/vendor/golang.org/x/xerrors/fmt.go b/vendor/golang.org/x/xerrors/fmt.go deleted file mode 100644 index 829862d..0000000 --- a/vendor/golang.org/x/xerrors/fmt.go +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package xerrors - -import ( - "fmt" - "strings" - "unicode" - "unicode/utf8" - - "golang.org/x/xerrors/internal" -) - -const percentBangString = "%!" - -// Errorf formats according to a format specifier and returns the string as a -// value that satisfies error. -// -// The returned error includes the file and line number of the caller when -// formatted with additional detail enabled. If the last argument is an error -// the returned error's Format method will return it if the format string ends -// with ": %s", ": %v", or ": %w". If the last argument is an error and the -// format string ends with ": %w", the returned error implements an Unwrap -// method returning it. -// -// If the format specifier includes a %w verb with an error operand in a -// position other than at the end, the returned error will still implement an -// Unwrap method returning the operand, but the error's Format method will not -// return the wrapped error. -// -// It is invalid to include more than one %w verb or to supply it with an -// operand that does not implement the error interface. The %w verb is otherwise -// a synonym for %v. -func Errorf(format string, a ...interface{}) error { - format = formatPlusW(format) - // Support a ": %[wsv]" suffix, which works well with xerrors.Formatter. - wrap := strings.HasSuffix(format, ": %w") - idx, format2, ok := parsePercentW(format) - percentWElsewhere := !wrap && idx >= 0 - if !percentWElsewhere && (wrap || strings.HasSuffix(format, ": %s") || strings.HasSuffix(format, ": %v")) { - err := errorAt(a, len(a)-1) - if err == nil { - return &noWrapError{fmt.Sprintf(format, a...), nil, Caller(1)} - } - // TODO: this is not entirely correct. The error value could be - // printed elsewhere in format if it mixes numbered with unnumbered - // substitutions. With relatively small changes to doPrintf we can - // have it optionally ignore extra arguments and pass the argument - // list in its entirety. - msg := fmt.Sprintf(format[:len(format)-len(": %s")], a[:len(a)-1]...) - frame := Frame{} - if internal.EnableTrace { - frame = Caller(1) - } - if wrap { - return &wrapError{msg, err, frame} - } - return &noWrapError{msg, err, frame} - } - // Support %w anywhere. - // TODO: don't repeat the wrapped error's message when %w occurs in the middle. - msg := fmt.Sprintf(format2, a...) - if idx < 0 { - return &noWrapError{msg, nil, Caller(1)} - } - err := errorAt(a, idx) - if !ok || err == nil { - // Too many %ws or argument of %w is not an error. Approximate the Go - // 1.13 fmt.Errorf message. - return &noWrapError{fmt.Sprintf("%sw(%s)", percentBangString, msg), nil, Caller(1)} - } - frame := Frame{} - if internal.EnableTrace { - frame = Caller(1) - } - return &wrapError{msg, err, frame} -} - -func errorAt(args []interface{}, i int) error { - if i < 0 || i >= len(args) { - return nil - } - err, ok := args[i].(error) - if !ok { - return nil - } - return err -} - -// formatPlusW is used to avoid the vet check that will barf at %w. -func formatPlusW(s string) string { - return s -} - -// Return the index of the only %w in format, or -1 if none. -// Also return a rewritten format string with %w replaced by %v, and -// false if there is more than one %w. -// TODO: handle "%[N]w". -func parsePercentW(format string) (idx int, newFormat string, ok bool) { - // Loosely copied from golang.org/x/tools/go/analysis/passes/printf/printf.go. - idx = -1 - ok = true - n := 0 - sz := 0 - var isW bool - for i := 0; i < len(format); i += sz { - if format[i] != '%' { - sz = 1 - continue - } - // "%%" is not a format directive. - if i+1 < len(format) && format[i+1] == '%' { - sz = 2 - continue - } - sz, isW = parsePrintfVerb(format[i:]) - if isW { - if idx >= 0 { - ok = false - } else { - idx = n - } - // "Replace" the last character, the 'w', with a 'v'. - p := i + sz - 1 - format = format[:p] + "v" + format[p+1:] - } - n++ - } - return idx, format, ok -} - -// Parse the printf verb starting with a % at s[0]. -// Return how many bytes it occupies and whether the verb is 'w'. -func parsePrintfVerb(s string) (int, bool) { - // Assume only that the directive is a sequence of non-letters followed by a single letter. - sz := 0 - var r rune - for i := 1; i < len(s); i += sz { - r, sz = utf8.DecodeRuneInString(s[i:]) - if unicode.IsLetter(r) { - return i + sz, r == 'w' - } - } - return len(s), false -} - -type noWrapError struct { - msg string - err error - frame Frame -} - -func (e *noWrapError) Error() string { - return fmt.Sprint(e) -} - -func (e *noWrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) } - -func (e *noWrapError) FormatError(p Printer) (next error) { - p.Print(e.msg) - e.frame.Format(p) - return e.err -} - -type wrapError struct { - msg string - err error - frame Frame -} - -func (e *wrapError) Error() string { - return fmt.Sprint(e) -} - -func (e *wrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) } - -func (e *wrapError) FormatError(p Printer) (next error) { - p.Print(e.msg) - e.frame.Format(p) - return e.err -} - -func (e *wrapError) Unwrap() error { - return e.err -} diff --git a/vendor/golang.org/x/xerrors/format.go b/vendor/golang.org/x/xerrors/format.go deleted file mode 100644 index 1bc9c26..0000000 --- a/vendor/golang.org/x/xerrors/format.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package xerrors - -// A Formatter formats error messages. -type Formatter interface { - error - - // FormatError prints the receiver's first error and returns the next error in - // the error chain, if any. - FormatError(p Printer) (next error) -} - -// A Printer formats error messages. -// -// The most common implementation of Printer is the one provided by package fmt -// during Printf (as of Go 1.13). Localization packages such as golang.org/x/text/message -// typically provide their own implementations. -type Printer interface { - // Print appends args to the message output. - Print(args ...interface{}) - - // Printf writes a formatted string. - Printf(format string, args ...interface{}) - - // Detail reports whether error detail is requested. - // After the first call to Detail, all text written to the Printer - // is formatted as additional detail, or ignored when - // detail has not been requested. - // If Detail returns false, the caller can avoid printing the detail at all. - Detail() bool -} diff --git a/vendor/golang.org/x/xerrors/frame.go b/vendor/golang.org/x/xerrors/frame.go deleted file mode 100644 index 0de628e..0000000 --- a/vendor/golang.org/x/xerrors/frame.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package xerrors - -import ( - "runtime" -) - -// A Frame contains part of a call stack. -type Frame struct { - // Make room for three PCs: the one we were asked for, what it called, - // and possibly a PC for skipPleaseUseCallersFrames. See: - // https://go.googlesource.com/go/+/032678e0fb/src/runtime/extern.go#169 - frames [3]uintptr -} - -// Caller returns a Frame that describes a frame on the caller's stack. -// The argument skip is the number of frames to skip over. -// Caller(0) returns the frame for the caller of Caller. -func Caller(skip int) Frame { - var s Frame - runtime.Callers(skip+1, s.frames[:]) - return s -} - -// location reports the file, line, and function of a frame. -// -// The returned function may be "" even if file and line are not. -func (f Frame) location() (function, file string, line int) { - frames := runtime.CallersFrames(f.frames[:]) - if _, ok := frames.Next(); !ok { - return "", "", 0 - } - fr, ok := frames.Next() - if !ok { - return "", "", 0 - } - return fr.Function, fr.File, fr.Line -} - -// Format prints the stack as error detail. -// It should be called from an error's Format implementation -// after printing any other error detail. -func (f Frame) Format(p Printer) { - if p.Detail() { - function, file, line := f.location() - if function != "" { - p.Printf("%s\n ", function) - } - if file != "" { - p.Printf("%s:%d\n", file, line) - } - } -} diff --git a/vendor/golang.org/x/xerrors/internal/internal.go b/vendor/golang.org/x/xerrors/internal/internal.go deleted file mode 100644 index 89f4eca..0000000 --- a/vendor/golang.org/x/xerrors/internal/internal.go +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package internal - -// EnableTrace indicates whether stack information should be recorded in errors. -var EnableTrace = true diff --git a/vendor/golang.org/x/xerrors/wrap.go b/vendor/golang.org/x/xerrors/wrap.go deleted file mode 100644 index 9a3b510..0000000 --- a/vendor/golang.org/x/xerrors/wrap.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package xerrors - -import ( - "reflect" -) - -// A Wrapper provides context around another error. -type Wrapper interface { - // Unwrap returns the next error in the error chain. - // If there is no next error, Unwrap returns nil. - Unwrap() error -} - -// Opaque returns an error with the same error formatting as err -// but that does not match err and cannot be unwrapped. -func Opaque(err error) error { - return noWrapper{err} -} - -type noWrapper struct { - error -} - -func (e noWrapper) FormatError(p Printer) (next error) { - if f, ok := e.error.(Formatter); ok { - return f.FormatError(p) - } - p.Print(e.error) - return nil -} - -// Unwrap returns the result of calling the Unwrap method on err, if err implements -// Unwrap. Otherwise, Unwrap returns nil. -func Unwrap(err error) error { - u, ok := err.(Wrapper) - if !ok { - return nil - } - return u.Unwrap() -} - -// Is reports whether any error in err's chain matches target. -// -// An error is considered to match a target if it is equal to that target or if -// it implements a method Is(error) bool such that Is(target) returns true. -func Is(err, target error) bool { - if target == nil { - return err == target - } - - isComparable := reflect.TypeOf(target).Comparable() - for { - if isComparable && err == target { - return true - } - if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) { - return true - } - // TODO: consider supporing target.Is(err). This would allow - // user-definable predicates, but also may allow for coping with sloppy - // APIs, thereby making it easier to get away with them. - if err = Unwrap(err); err == nil { - return false - } - } -} - -// As finds the first error in err's chain that matches the type to which target -// points, and if so, sets the target to its value and returns true. An error -// matches a type if it is assignable to the target type, or if it has a method -// As(interface{}) bool such that As(target) returns true. As will panic if target -// is not a non-nil pointer to a type which implements error or is of interface type. -// -// The As method should set the target to its value and return true if err -// matches the type to which target points. -func As(err error, target interface{}) bool { - if target == nil { - panic("errors: target cannot be nil") - } - val := reflect.ValueOf(target) - typ := val.Type() - if typ.Kind() != reflect.Ptr || val.IsNil() { - panic("errors: target must be a non-nil pointer") - } - if e := typ.Elem(); e.Kind() != reflect.Interface && !e.Implements(errorType) { - panic("errors: *target must be interface or implement error") - } - targetType := typ.Elem() - for err != nil { - if reflect.TypeOf(err).AssignableTo(targetType) { - val.Elem().Set(reflect.ValueOf(err)) - return true - } - if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) { - return true - } - err = Unwrap(err) - } - return false -} - -var errorType = reflect.TypeOf((*error)(nil)).Elem() diff --git a/vendor/gopkg.in/yaml.v2/.travis.yml b/vendor/gopkg.in/yaml.v2/.travis.yml index 055480b..7348c50 100644 --- a/vendor/gopkg.in/yaml.v2/.travis.yml +++ b/vendor/gopkg.in/yaml.v2/.travis.yml @@ -11,6 +11,7 @@ go: - "1.11.x" - "1.12.x" - "1.13.x" + - "1.14.x" - "tip" go_import_path: gopkg.in/yaml.v2 diff --git a/vendor/gopkg.in/yaml.v2/apic.go b/vendor/gopkg.in/yaml.v2/apic.go index 1f7e87e..acf7140 100644 --- a/vendor/gopkg.in/yaml.v2/apic.go +++ b/vendor/gopkg.in/yaml.v2/apic.go @@ -79,6 +79,8 @@ func yaml_parser_set_encoding(parser *yaml_parser_t, encoding yaml_encoding_t) { parser.encoding = encoding } +var disableLineWrapping = false + // Create a new emitter object. func yaml_emitter_initialize(emitter *yaml_emitter_t) { *emitter = yaml_emitter_t{ @@ -87,6 +89,9 @@ func yaml_emitter_initialize(emitter *yaml_emitter_t) { states: make([]yaml_emitter_state_t, 0, initial_stack_size), events: make([]yaml_event_t, 0, initial_queue_size), } + if disableLineWrapping { + emitter.best_width = -1 + } } // Destroy an emitter object. diff --git a/vendor/gopkg.in/yaml.v2/yaml.go b/vendor/gopkg.in/yaml.v2/yaml.go index 89650e2..3081388 100644 --- a/vendor/gopkg.in/yaml.v2/yaml.go +++ b/vendor/gopkg.in/yaml.v2/yaml.go @@ -175,7 +175,7 @@ func unmarshal(in []byte, out interface{}, strict bool) (err error) { // Zero valued structs will be omitted if all their public // fields are zero, unless they implement an IsZero // method (see the IsZeroer interface type), in which -// case the field will be included if that method returns true. +// case the field will be excluded if IsZero returns true. // // flow Marshal using a flow style (useful for structs, // sequences and maps). @@ -464,3 +464,15 @@ func isZero(v reflect.Value) bool { } return false } + +// FutureLineWrap globally disables line wrapping when encoding long strings. +// This is a temporary and thus deprecated method introduced to faciliate +// migration towards v3, which offers more control of line lengths on +// individual encodings, and has a default matching the behavior introduced +// by this function. +// +// The default formatting of v2 was erroneously changed in v2.3.0 and reverted +// in v2.4.0, at which point this function was introduced to help migration. +func FutureLineWrap() { + disableLineWrapping = true +} diff --git a/vendor/modules.txt b/vendor/modules.txt index ed08eb8..2abfd15 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,50 +1,44 @@ -# github.com/PagerDuty/go-pagerduty v1.1.2 -## explicit; go 1.12 +# github.com/PagerDuty/go-pagerduty v1.8.0 +## explicit; go 1.19 github.com/PagerDuty/go-pagerduty # github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 ## explicit # github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d ## explicit; go 1.12 github.com/cpuguy83/go-md2man/v2/md2man -# github.com/google/go-cmp v0.5.4 -## explicit; go 1.8 +# github.com/google/go-cmp v0.6.0 +## explicit; go 1.13 github.com/google/go-cmp/cmp github.com/google/go-cmp/cmp/cmpopts github.com/google/go-cmp/cmp/internal/diff github.com/google/go-cmp/cmp/internal/flags github.com/google/go-cmp/cmp/internal/function github.com/google/go-cmp/cmp/internal/value -# github.com/google/go-querystring v1.0.0 -## explicit +# github.com/google/go-querystring v1.1.0 +## explicit; go 1.10 github.com/google/go-querystring/query -# github.com/gorilla/websocket v1.2.0 -## explicit +# github.com/gorilla/websocket v1.4.2 +## explicit; go 1.12 github.com/gorilla/websocket # github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2 ## explicit github.com/matryer/try -# github.com/pkg/errors v0.8.0 -## explicit -github.com/pkg/errors # github.com/russross/blackfriday/v2 v2.0.1 ## explicit github.com/russross/blackfriday/v2 # github.com/shurcooL/sanitized_anchor_name v1.0.0 ## explicit github.com/shurcooL/sanitized_anchor_name -# github.com/slack-go/slack v0.6.3 -## explicit; go 1.13 +# github.com/slack-go/slack v0.14.0 +## explicit; go 1.16 github.com/slack-go/slack +github.com/slack-go/slack/internal/backoff github.com/slack-go/slack/internal/errorsx github.com/slack-go/slack/internal/timex github.com/slack-go/slack/slackutilsx # github.com/urfave/cli/v2 v2.1.1 ## explicit; go 1.11 github.com/urfave/cli/v2 -# golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 -## explicit; go 1.11 -golang.org/x/xerrors -golang.org/x/xerrors/internal -# gopkg.in/yaml.v2 v2.2.8 -## explicit +# gopkg.in/yaml.v2 v2.4.0 +## explicit; go 1.15 gopkg.in/yaml.v2