-
Notifications
You must be signed in to change notification settings - Fork 9
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
Make explicit the use of the parameter GITHUB_TOKEN
when use create_release
#420
Make explicit the use of the parameter GITHUB_TOKEN
when use create_release
#420
Conversation
…_client The use of the github_token under the hood was causing visibility problems about the token being mandatory and necessary to execute some actions on the API
At the moment of the release creation, the githubtoken can be passed as a parameter. In the cases that the param is nil or empty, we fallback to using the github_token from the runtime environment.
At the calling of the action the client has the possibility to use the githubtoken inside the create_relese action, the use is also documented in the ConfigItens
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.
Hey @raafaelima 👋
Thanks for the draft PR. Gave it a first look, see feedback below.
The solution proposed was to change the implementation to make the token mandatory at the github_client level, and make the create_release receive an optional argument that will serve as the de facto GitHubToken.
Sorry if I wasn't clear in my comment on your P2. I see that I indeed mentioned making the token mandatory at the github_client
level, but what I really had in mind was to make the token mandatory at the GitHubHelper
level instead. That is, have the GitHubHelper
never look up the env var itself to try to find the token there (and so for all of the methods the helper defines) — in other words, get rid of def self.github_token!
entirely — and always have each of the calling actions (like CreateReleaseAction
, CloseMilestoneAction
, etc) be the ones providing the token to the helper — and be the ones porting the built-in fallback to the env_var if needed)
Given this is a helper change affecting the way we use the GitHub client, the only E2E way to do it is by triggering the Github release using either a Fastlane lane or Gradle function with the new parameter set. Also, those scenarios are covered by unit testing
Given the scope of the change and of what we want to test, there is no need of any e2e test to test this change and feature. If your Unit Tests are correctly written and cover all the cases that this PR wants to address, they should be plenty enough.
lib/fastlane/plugin/wpmreleasetoolkit/actions/common/create_release_action.rb
Outdated
Show resolved
Hide resolved
lib/fastlane/plugin/wpmreleasetoolkit/actions/common/create_release_action.rb
Outdated
Show resolved
Hide resolved
lib/fastlane/plugin/wpmreleasetoolkit/actions/common/create_release_action.rb
Outdated
Show resolved
Hide resolved
Hey @AliSoftware 😄 Thanks for the feedback on the PR, I went through your review and leave some comments to confirm my assumptions about specific suggestions that you make. And about your overall comment
I'm sorry for that, Yeah, I totally misunderstand the suggestion 😞 |
The github_client was removed in favor of instantiating the GithubClient at every instantiation of the helper, in that way we expose the use of the token and making more difficult to miss it at the use of this helper
The need of the GitHub token to use the actions is now mandatory, so the ConfigItens and parameters were updated to reflect those changes
lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_download_file_by_version.rb
Outdated
Show resolved
Hide resolved
lib/fastlane/plugin/wpmreleasetoolkit/actions/common/comment_on_pr.rb
Outdated
Show resolved
Hide resolved
…ken to access_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.
I'll have to go on an errand so I'll get back later to do a more in-depth review and check I haven't forgotten to comment on anything, but here's a first feedback on the recent changes you made since my last review 🙂
lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_download_file_by_version.rb
Outdated
Show resolved
Hide resolved
lib/fastlane/plugin/wpmreleasetoolkit/actions/common/close_milestone_action.rb
Outdated
Show resolved
Hide resolved
lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_download_file_by_version.rb
Outdated
Show resolved
Hide resolved
lib/fastlane/plugin/wpmreleasetoolkit/actions/common/setbranchprotection_action.rb
Outdated
Show resolved
Hide resolved
lib/fastlane/plugin/wpmreleasetoolkit/actions/common/setfrozentag_action.rb
Outdated
Show resolved
Hide resolved
def self.github_token! | ||
token = [ | ||
'GHHELPER_ACCESS', # For historical reasons / backward compatibility | ||
'GITHUB_TOKEN', # Used by the `gh` CLI tool | ||
].map { |key| ENV[key] } | ||
.compact | ||
.first | ||
|
||
token || UI.user_error!('Please provide a GitHub authentication token via the `GITHUB_TOKEN` environment variable') | ||
end |
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.
Yay! Bye bye GithubHelper#github_token!
method and the need for the helper to know or look at the env vars 🎉
lib/fastlane/plugin/wpmreleasetoolkit/actions/common/close_milestone_action.rb
Outdated
Show resolved
Hide resolved
lib/fastlane/plugin/wpmreleasetoolkit/actions/common/removebranchprotection_action.rb
Outdated
Show resolved
Hide resolved
…from GithubClient
The GithubHelper must be the only entry point to access the OktoClient, with that we have more control about the correct parameters and configurations being set and centralizes all the logic on a single point
When I request your re-review I think I misclick and remove @mokagio as the other reviewer, could you add him again? 😅 |
Requested changes were addressed since
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.
So far so good, and apart from the small typos I've found, I think this is ready to go 👍
I'd love to get an additional review from Gio to get his thoughts before merging tho 😉
spec/github_helper_spec.rb
Outdated
donwloaded_file = described_class.download_file_from_tag(repository: test_repo, tag: test_tag, file_path: test_file, download_folder: './') | ||
expect(donwloaded_file).to be_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.
💄 Small typo 😉
donwloaded_file = described_class.download_file_from_tag(repository: test_repo, tag: test_tag, file_path: test_file, download_folder: './') | |
expect(donwloaded_file).to be_nil | |
downloaded_file = described_class.download_file_from_tag(repository: test_repo, tag: test_tag, file_path: test_file, download_folder: './') | |
expect(downloaded_file).to be_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.
Oh... more than 10 years of speaking/writing in English and I still can't write the word download right at first hahaha
Sorry for that 😅
spec/github_helper_spec.rb
Outdated
donwloaded_file = described_class.download_file_from_tag(repository: test_repo, tag: test_tag, file_path: test_file, download_folder: tmpdir) | ||
expect(donwloaded_file).to eq(dst_file) |
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.
💄 Same typo 😄
donwloaded_file = described_class.download_file_from_tag(repository: test_repo, tag: test_tag, file_path: test_file, download_folder: tmpdir) | |
expect(donwloaded_file).to eq(dst_file) | |
downloaded_file = described_class.download_file_from_tag(repository: test_repo, tag: test_tag, file_path: test_file, download_folder: tmpdir) | |
expect(downloaded_file).to eq(dst_file) |
# Remove the protection of a single branch from a repository | ||
# | ||
# @param [String] repository The repository name (including the organization) | ||
# @param [String] number The branch 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.
# @param [String] number The branch name | |
# @param [String] branch The branch name |
@@ -57,6 +58,7 @@ def self.available_options | |||
description: 'The prefix which is used in the GitHub release title', | |||
type: String, | |||
optional: true), | |||
Fastlane::Helper::GithubHelper.github_token_config_item |
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.
I just triggered CI on your PR (FYI, because you're working on a fork, CI isn't run automatically—for security reasons— like it is for usual PRs), and got a rubocop warning on this line (see here):
lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_download_file_by_version.rb:61:11: C: [Correctable] Style/TrailingCommaInArrayLiteral: Put a comma after the last item of a multiline array.
Fastlane::Helper::GithubHelper.github_token_config_item
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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.
I do not have access to see that buildkite link 😢
I fixed the warning, hope everything pass now 👍
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.
I've retriggered the CI on your latest commit, let's see if CI checks go green on your PR after that 🙂
# Protects a single branch from a repository | ||
# | ||
# @param [String] repository The repository name (including the organization) | ||
# @param [String] number The branch 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.
# @param [String] number The branch name | |
# @param [String] branch The branch name |
Hey @AliSoftware, I would like your feedback if this makes sense. 💭 🤔 If I remember well, the As we changed the implementation and now the GithubHelper is not "static" accessed anymore, all those methods should lose the This also implies that the specs of GithubClient should now manually instantiate the class, instead of only relying on the use of #Instead of def self.comment_on_pr, it should be
def comment_on_pr(project_slug:, pr_number:, body:, reuse_identifier: SecureRandom.uuid) { ... } #Instead od accessing the method as described_class.comment_on_pr(...), should be
def comment_on_pr
helper = described_class.new(github_token: 'TestToken123')
helper.comment_on_pr(
project_slug: 'test/test',
pr_number: 1234,
body: 'Test',
reuse_identifier: 'test-id'
)
end Is my assumption correct? |
Ooh, great catch @raafaelima, I'm surprised that I totally missed that in my previous reviews, you're correct indeed 👍 One would have hoped that the unit tests would catch that issue, but given how rspec works and Ruby being a dynamic language, we were unlucky to have Ruby and rspec's dynamic nature at our disadvantage here. Indeed, as the specs currently call the static methods, when their implementation mention |
In the past all the methods use the self to be accessed as class methods, as is now mandatory to instanciate the GithubHelper, the methods are now converted in instance methods.
The invocation of the actions protect/unprotect branch was with the wrong parameters at the call of the method on the cliend
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.
Nice job Rafael! Nothing to add to Olivier's comments, only left a few minor suggestion/nitpicks.
I'm leaving a comment not an approval merely because I'm late to the party and want Olivier, who has more context, to have the final word.
This PR ended up requiring a few rounds of back and forth. That's common, in particular at the start of a trial, so no worries at all 😄
I find a great way to help PRs move fast is to make smaller PRs. Just curious if, in hindsight, there's any way you think we could have split this piece of work in smaller components. (I'm just chatting here, there is no "right" answer to this question 👌 )
spec/github_helper_spec.rb
Outdated
before do | ||
allow(Octokit::Client).to receive(:new).and_return(client) | ||
end | ||
|
||
it 'properly passes the token all the way down to the Octokit::Client' do | ||
expect(Octokit::Client).to receive(:new).with(access_token: 'Fake-GitHubToken-123') | ||
described_class.new(github_token: 'Fake-GitHubToken-123') | ||
end |
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.
What do you think of simplifying this to
before do | |
allow(Octokit::Client).to receive(:new).and_return(client) | |
end | |
it 'properly passes the token all the way down to the Octokit::Client' do | |
expect(Octokit::Client).to receive(:new).with(access_token: 'Fake-GitHubToken-123') | |
described_class.new(github_token: 'Fake-GitHubToken-123') | |
end | |
it 'properly passes the token all the way down to the Octokit::Client' do | |
allow(Octokit::Client).to receive(:new).and_return(client) | |
expect(Octokit::Client).to receive(:new).with(access_token: 'Fake-GitHubToken-123') | |
described_class.new(github_token: 'Fake-GitHubToken-123') | |
end |
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.
Also, testing that a certain method on a certain object is called with a certain parameter makes the test heavily dependent on the implementation details of the system under test as well as the object it checks.
There's nothing bad about this approach, but it can backfire because we want to be able to change the code's implementation (i.e. refactor) many times and use the tests to verify our changes haven't broken the code's behavior. Because, at the end of the day, it's the behavior that really matters.
I think this test in particular is a bit of an edge case, because what we are testing is inherently an implementation detail: Whether Octokit::Client
received the correct token. So, I think it's okay to keep the test as is.
Just curious, did you happen to find other possible ways to verify the behavior of the token having been passed to Octokit::Client
? (Apologies if this has already been discussed. There are a lot of resolved conversations in this PR and I haven't expanded them all).
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.
Hey @mokagio, thanks for your feedback!
I really appreciate it! 😁
Just curious, did you happen to find other possible ways to verify the behavior of the token having been passed to Octokit::Client?
To be honest, yes I have some other options that I was considering to follow here to validate the token instead of expect Octokit::Client::new
having its value. I list them here with the tradeoffs that I consider them to have, and really would like yours and @AliSoftware inputs about them.
Reinstate the empty/nil validation at the token
Before I start this PR, on the github_helper
we have the github_token
function that tries to get the token from the Env variables and raised an error if is not present. We can have the same validation in place at our initialize
function as the first thing, something like this:
token || UI.user_error!('Please provide a GitHub authentication token')
With this, we can validate the presence of the token, no matter what we're going to do with it later and, as a bonus, we remove the Octokit::Client::new
expectations as it is now on this PR. The drawback here is that we "lose" our assurance that the Octokit::Client
receives the token that we pass at the helper, and no other thing in the place instead.
Although this will be also validated by the ConfigItens at the actions and is a very edge case having a GithubHelper without the token present, with this double check we prevent any changes on that requirement of having the token, in the future.
Test the API Call that Oktokit does at the initialization
This other option exists, TBH I do not like it as much as the first as I see no value in testing things inside 3rd-party libraries.
Looking at the library source code, at the moment that the Octokit::Client
is initialized with the access token, it does a request to the GitHub API under the hood, passing that token as one of the Headers of that request. I imagine that is due to validate if the token is indeed valid before proceeding with anything.
GET https://api.github.com/user with headers {'Accept'=>'application/vnd.github.v3+json', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization'=>'token Fake-GitHubToken-123', 'Content-Type'=>'application/json', 'User-Agent'=>'Octokit Ruby Gem 4.21.0'}
So, we could stub that call and check if the token that we passed is indeed in that request. However, as I said before, this is an implementation detail at the Octokit::Client
side, which is most certainly also covered by their internal unit tests.
Although we remove the dependency of knowing if the Octokit::Client::new
has our correct token, we also introduce another dependency to an internal thing that the library does and thus can be changed in the future also being a dangerous drawback.
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.
Thank you for thinking through some options @raafaelima 😄 As I mentioned in my earlier comment, I think the code as is has acceptable tradeoff and wouldn't endorse changing it, unless someone can come with an obviously better option, of course. Still, it's fun to toss ideas around and consider options.
Reinstate the empty/nil validation at the token
I think this test would be less valuable than what we have already. Checking that GithubHelper
errors if the token is nil
doesn't prevent callers from passing nil
to it, and, as you point out, it wouldn't tell us whether Octokit::Client
gets that token.
We might be better served by static types checking for this. One day...
Test the API Call that Oktokit does at the initialization
[...] we also introduce another dependency to an internal thing that the library does and thus can be changed in the future also being a dangerous drawback.
This is interesting. I kinda like the idea to check whether an HTTP request to the GitHub API is made with the given token. This effect should stay the same if for some reason we changed how we interface with the API in the future—we always end up needing to talk to the GitHub APIs at some point.
On the other hand, a great advantage of delegating the GitHub API interaction implementation to OctoKit is that we don't need to worry about these lower level details. Maybe accepting a test a bit more towards the implementation details side of the spectrum is worth not having to worry about all the GitHub API side of thing.
About this @mokagio, I realize that I make this PR more than I was initially expecting it to be as I start to add unit tests and some minor refactorings (as the wrap of client functions) to it, instead of only staying with the changes around the Token and client. The ideal scenario here is I would have split in at least other two PRs, stacked on top of this one, one with the wrapper and their unit tests, and the other one to the ConfigItem and their unit tests, It would have been quicker and the diff not be so long for you folks to review. |
CHANGELOG.md
Outdated
@@ -6,7 +6,9 @@ | |||
|
|||
### Breaking Changes | |||
|
|||
_None_ | |||
- Removed support for the deprecated `GHHELPER_ACCESS` in favor of `GITHUB_TOKEN` as the default environment variable to set the GitHub API token. [#420] | |||
- The `github_client:` parameter (aka `ConfigItem`) is now mandatory for all Fastlane actions that use the GitHub API. [#420] |
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.
- Typo fix
- Adjusting wording, as in practice, many clients won't provide
github_token: …
as part of the parameters used in their call sites and will instead just rely onGITHUB_TOKEN
env var being set (e.g. this call site won't require any change for it to continue working)
- The `github_client:` parameter (aka `ConfigItem`) is now mandatory for all Fastlane actions that use the GitHub API. [#420] | |
- The `github_token:` parameter (aka `ConfigItem`)–or using the corresponding `GITHUB_TOKEN` env var to provide it a value–is now mandatory for all Fastlane actions that use the GitHub API. [#420] |
PS: Btw, one good news to note is that, despite those items indeed being "breaking changes" (for any call site of any of those actions that relied on GHHELPER_ACCESS
env var instead of GITHUB_TOKEN
, and/or for call sites of comment_on_pr
that used access_token:
parameter explicitly), in practice I doubt that many client app relied on any of those, because most of our client apps and CI rely on the GITHUB_TOKEN
env var already (as opposed to access_token:
param or deprecated GHHELPER_ACCESS
env var) to provide the token, which is something that will continue working as before.
So while this is correct for those changes to be under "Breaking Changes", the good news is in practice most clients will have nothing to do to adopt 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.
- Last minute typo spotted
- Suggestion about using the magic
**
Hash-conversion Ruby operator on methods that haveoptions:
parameter. This is not a blocker and can totally addressed via a separate and subsequent PR if you prefer though.
Other than this, once CHANGELOG typo is fixed I think it's ready to land! 🎉
# @return [Milestone] A single milestone object | ||
# @see http://developer.github.com/v3/issues/milestones/#update-a-milestone | ||
# | ||
def update_milestone(repository:, number:, options:) |
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.
Nitpick (that I should have mentioned earlier, sorry): while I think having named parameters is way nicer for most cases, the case of options:
— especially it being a Hash
and the last parameter of the Ruby function that could gather additional arbitrary keywords/options — might be better served using the little-known but magic Hash-splat operator trick that Ruby supports:
def update_milestone(repository:, number:, options:) | |
def update_milestone(repository:, number:, **options) |
This will allow the call site to then just provide the options directly as if they were keywords (kinda "flattening the Hash into keyword parameters") without having to wrap those options into { … }
curly braces to make them a Hash. So the call site would look like this:
# Instead of:
# github_helper.update_milestone(repository: repository, number: milestone[:number], options: { title: mile_title })
# We'd have:
github_helper.update_milestone(repository: repository, number: milestone[:number], title: mile_title)
# Inside `update_milestone`'s implementation, the variable/parameter `options` would be a Hash equal to `{ title: mile_title }`
# Instead of:
# github_helper.update_milestone(repository: repository, number: milestone[:number], options: { state: 'closed' })
# We'd have:
github_helper.update_milestone(repository: repository, number: milestone[:number], state: 'closed')
# Inside `update_milestone`'s implementation, the variable/parameter `options` would be a Hash equal to `{ state: 'closed' }`
And thanks to the **
operator, the options
variable inside the update_milestone
's body would still be a Hash
like before. But at least that would make the call sites nicer, avoiding them to have an additional level to wrap the options into a Hash explicitly at call site 🙃
Demo (in the pry REPL)
[2] pry(main)> def foo(a:, b:, **options)
[2] pry(main)* puts "a: #{a.inspect}"
[2] pry(main)* puts "b: #{b.inspect}"
[2] pry(main)* puts "options: #{options.inspect}"
[2] pry(main)* end
=> :foo
[3] pry(main)> foo()
ArgumentError: missing keywords: :a, :b
from (pry):3:in `foo'
[4] pry(main)> foo(a: 1, b: 'hello')
a: 1
b: "hello"
options: {}
=> nil
[5] pry(main)> foo(a: 1, b: 'hello', size: 3, skip: false)
a: 1
b: "hello"
options: {:size=>3, :skip=>false}
This is really a nitpick and not a blocker per se, but I figured that since it's quite a little-known / advanced feature of Ruby, it would still be worth mentioning in case you wanted to adopt it 🙂
(Same for all the other actions similarly having an options:
keyword, of course)
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.
Perfect! I really like that suggestion, it will make the method calls even cleaner. 😄
And, as I said on Slack, I believe that will be better to address this on other PR as It is a small change, will be easier to review and track any other improvements like this.
`GHHELPER_REPO` used to be a meaningful name in release-toolkit, but as of version 6.0.0, that [is no longer the case](wordpress-mobile/release-toolkit#420). `GITHUB_REPO` is a much clearer and appropriate name for what the constant represents.
What does this solve?
The issue is about CI failing at the moment of doing a GitHub release, The reason for the failure was a 401 from the GitHub API. This happened because neither GHHELPER_ACCESS nor GITHUB_TOKEN was available in the environment at runtime, As stated in Issue#416.
How was it solved?
The solution proposed was to change the implementation to make the token mandatory at the
github_client level
, and make thecreate_release
receive an optional argument that will serve as the de facto GitHubToken. In addition, create a fallback mechanism to thecreate_release
function to use the environment variableGITHUB_TOKEN
when the parameter passed at the function is not present.A more verbose API in the actions by adding parameters for the tokens they expect will make it more difficult to forget their use of them. Making them invisible to our users can cause errors like the one on the issue.
Testing Instructions
Given this is a helper change affecting the way we use the GitHub client, the only E2E way to do it is by triggering the Github release using either a Fastlane lane or Gradle function with the new parameter set. Also, those scenarios are covered by unit testing
The expected behaviors are:
Example of Lane with the new githubtoken param set