-
Notifications
You must be signed in to change notification settings - Fork 35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adopt existing channel on create #113
Adopt existing channel on create #113
Conversation
go.mod
Outdated
@@ -14,6 +14,7 @@ require ( | |||
github.com/hashicorp/go-getter v1.5.5 // indirect | |||
github.com/hashicorp/go-multierror v1.1.1 | |||
github.com/hashicorp/hcl/v2 v2.10.0 // indirect | |||
github.com/hashicorp/terraform-plugin-log v0.2.0 // indirect |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
using tflog for some diagnostic output on the new code I added. Probably should be more logging throughout?
@@ -107,6 +114,17 @@ func resourceSlackConversationCreate(ctx context.Context, d *schema.ResourceData | |||
isPrivate := d.Get("is_private").(bool) | |||
|
|||
channel, err := client.CreateConversationContext(ctx, name, isPrivate) | |||
if err != nil && err.Error() == "name_taken" && d.Get("adopt_existing_channel").(bool) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we get the 'name_taken' error, and the new 'adopt_exsting_channel' flag is true, then find the existing channel and use it instead of a newly created channel.
// ensure unarchived first if adopting existing channel, else other calls below will fail | ||
if err := client.UnArchiveConversationContext(ctx, channel.ID); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if the channel is currently archived, even if it is configured to eventually be archived, we need to unarchive it as many of the calls below will fail if called on an archived channel.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NOTE! this api does not work with bot tokens (despite documentation to the contrary). The provider must be configured with a user token.
if err.Error() != "not_archived" { | ||
return diag.Errorf("couldn't unarchive conversation %s: %s", channel.ID, err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we really should never get this error, as we check that it's archived before unarchived, but I guess there could be race conditions with a user unarchiving it manually in between the two calls.
// find the existing channel. Sadly, there is no non-admin API to search by name, | ||
// so we must search through ALL the channels |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An extreme sadness with the existing slack api. There is an admin api for searching for a specific channel, but that would greatly limit the deployment ability of this provider, as it would need to be configured with an administrator user token.
slack/resource_conversation.go
Outdated
if rateLimitedError, ok := err.(*slack.RateLimitedError); ok { | ||
tflog.Warn(ctx, "rate limited for %f seconds", rateLimitedError.RetryAfter.Seconds()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
some slack workspaces have thousands of channels, so even going 200 at a time will possibly hit rate limiting. So we need to handle waiting and retrying.
select { | ||
case <-ctx.Done(): | ||
return nil, fmt.Errorf("canceled during pagination: %s", ctx.Err()) | ||
case <-time.After(rateLimitedError.RetryAfter): | ||
tflog.Debug(ctx, "done sleeping after rate limited") | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is similarly implemented to the internals of the paginated GetUsersContext in the slack-go library that this provider is built upon.
https://github.com/slack-go/slack/blob/34c7fd379e3526317abcc057ef7baa686cb0a42c/users.go#L379
slack/resource_conversation.go
Outdated
for _, c := range channels { | ||
tflog.Trace(ctx, "current channel %s", c.Name) | ||
if c.Name == name { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
check every single channel, but bail as soon as we find a match
@@ -246,7 +312,7 @@ func resourceSlackConversationUpdate(ctx context.Context, d *schema.ResourceData | |||
} else { | |||
if err := client.UnArchiveConversationContext(ctx, id); err != nil { | |||
if err.Error() != "not_archived" { | |||
return diag.Errorf("couldn't archive conversation %s: %s", id, err) | |||
return diag.Errorf("couldn't unarchive conversation %s: %s", id, err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is an unarchive, so the error was inaccurate.
@@ -246,7 +312,7 @@ func resourceSlackConversationUpdate(ctx context.Context, d *schema.ResourceData | |||
} else { | |||
if err := client.UnArchiveConversationContext(ctx, id); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note as above that this call does NOT work with bot tokens, only user tokens. :-(
Thank you very much for your contribution! And apologies for the late response. I've been quite busy and neglected this project. The change looks fine. In my view There are two things lacking: tests and documentation. I would ask you to at least document the new parameter Could you also update the PR with the latest changes in master? There are conflicts that can not be automerged. I will allow linters and tests to run for this PR. Once that is done I will happily merge it and release a new version. |
ok, thx! Will look into docs and whatever tests I can figure out. I actually wasn't sure how to test this, since it seems to want credentials for a slack account and I can't have it run its tests on our corporate account. |
Oh I see. Yes, you need a slack token to run the tests, set using |
05f9b93
to
1ffdcc6
Compare
docs/resources/conversation.md
Outdated
- [channels:read](https://api.slack.com/scopes/channels:read) (public channels) | ||
- [channels:manage](https://api.slack.com/scopes/channels:manage) (public channels) | ||
- [channels:join](https://api.slack.com/scopes/channels:join) (adopting existing public channels) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bot token separates 'join' permission from 'manage'
|
||
If using `user` tokens: | ||
- [channels:read](https://api.slack.com/scopes/channels:read) (public channels) | ||
- [channels:write](https://api.slack.com/scopes/channels:manage) (public channels) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
user token uses 'write' rather than 'manage', and 'write' includes join permission already.
- [groups:read](https://api.slack.com/scopes/groups:read) (private channels) | ||
- [groups:write](https://api.slack.com/scopes/groups:write) (private channels) | ||
|
||
The Slack API methods used by the resource are: | ||
|
||
- [conversations.create](https://api.slack.com/methods/conversations.create) | ||
- [conversations.join](https://api.slack.com/methods/conversations.join) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we need to be able to join existing unarchived channels
docs/resources/conversation.md
Outdated
```hcl | ||
resource "slack_conversation" "adopted" { | ||
name = "my-channel02" | ||
topic = "If already existing, channel will be adopted, and existing users not kicked" | ||
permanent_members = [] | ||
adopt_existing_channel = true | ||
remove_non_members = false | ||
} | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An example of the two new parameters in use
docs/resources/conversation.md
Outdated
`archive | none`. Note that when set to `none` the conversation will be left | ||
as it is and as a result any subsequent runs of terraform apply with the same | ||
name will fail. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that I think, regardless of whether archive or none is specified, subsequent applies will fail unless adopt_existing_channel is set to true.
docs/resources/conversation.md
Outdated
- `adopt_existing_channel` (Optional, Default `false`) indicates that an existing | ||
channel with the same name should be adopted by terraform and put under state | ||
management. If the existing channel is archived, it will be unarchived. (Note: for | ||
unarchiving of existing channels to work correctly, you _must_ use a user token, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added the note about the need for 'user' tokens
docs/resources/conversation.md
Outdated
- `remove_non_members` (Optional, Default `true`) indicates that members of the | ||
channel that aren't listed in the `permament_members` array will be kicked. (Note: | ||
your Slack configuration may restrict/prohibit kicking of members from channels | ||
except when using administrator credentials) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is especially needed for adopting existing channels, as permission to remove non members may be denied by slack configuration.
@pablovarela I added docs, and also added another needed parameter to make this work on slack servers that have restricted 'kick' permissions. |
Ok, I saw that other PR, but looked like it wasn't going anywhere, and this was critically needed to make the adopting of existing channels work correctly. Will re-base on master and resubmit the PR sometime later today. |
4299839
to
5290d3f
Compare
@pablovarela rebased on master, removing |
@pablovarela any chance this could get merged soon? |
5290d3f
to
f137252
Compare
@pablovarela I see there was another PR that jumped ahead of this one, causing merge conflicts again. Rebased again. Please review and merge ASAP. |
@pablovarela ping |
on it |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are some linting issues (a magic number and some markdown ones), and if I ignore linting the tests are not passing. You can run the tests locally using make.
I will provide you with a token that I will change later, so you can test. Please set the environment variable SLACK_TOKEN=xoxp-1437037581287-1448689260021-1448696433877-1e561acc1efe312956ea7b00d629f805
and run the tests
slack/resource_conversation.go
Outdated
for !paginationComplete { | ||
channels, nextCursor, err := client.GetConversationsContext(ctx, &slack.GetConversationsParameters{ | ||
Cursor: cursor, | ||
Limit: 200, // 100 is default, docs recommend no more than 200, but 1000 is the max |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this magic number is from the documentation of the slack api. See, for instance, a similar usage in their golang library.
https://github.com/slack-go/slack/blob/master/users.go#L305
made more changes to satisfy linter, at cost of making documentation harder to read in edit mode. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The linter is still complaining. If you run make docs-lint-fix
it will fix the remaining linter issues.
I run that locally, and the linter passes. But the tests still fails, for example
=== CONT TestAccSlackConversationTest/add_permanent_members
resource_conversation_test.go:162: Step 1/3 error: Error running apply: exit status 1
Error: api user could not join conversation: method_not_supported_for_channel_type
with slack_conversation.test,
on terraform_plugin_test.tf line 2, in resource "slack_conversation" "test":
2: resource slack_conversation test {
If a new 'adopt_existing_channel' attribute is set 'true' (default 'false' for backwards compatibility), an existing channel with the same name and type will be 'adopted' by the terraform provider, and unarchived if necessary, to serve as the newly 'created' channel. This brings parity with the existing provider functionality of archiving or abandoning channels on terraform resource destroy. Now you can do a 'terraform apply', 'terraform destroy', and 'terraform apply' again without errors.
membership operations (needed when adopting existing unarchived channels)
The upstream provider updated the sdk version, which pulled in this new version. The new version has breaking API changes to logging to make it more 'structured'.
a8fcfa6
to
e6cf23a
Compare
- some channel types (notably private ones) can't be joined. - ephemeral attributes shouldn't be checked
This is not part of 1.1.24 release. Thank you so much for your contribution and your patience. |
Thx! This is not part of 1.1.24? It looks like it's in the release notes, tho. |
it is part of 1.1.24
|
If a new 'adopt_existing_channel' attribute is set 'true' (default 'false'
for backwards compatibility), an existing channel with the same name
and type will be 'adopted' by the terraform provider, and unarchived
if necessary, to serve as the newly 'created' channel.
This brings parity with the existing provider functionality of archiving
or abandoning channels on terraform resource destroy. Now you can do
a 'terraform apply', 'terraform destroy', and 'terraform apply' again
without errors.
Fixes #112