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
JENKINS-45789 Use Credentials Plugin for JIRA Global Configuration User #140
Conversation
|
||
// HACK, update final fields via reflection, see jls 17.5.3, https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.3 | ||
if (StringUtils.isNotBlank(credentialsId)) { | ||
StandardUsernamePasswordCredentials credentials = CredentialsHelper.lookupSystemCredentials(credentialsId, url != null ? url.toExternalForm() : null); |
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.
could we move url null check inside lookupSystemCredentials
?
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.
Good catch!
@@ -14,7 +14,7 @@ | |||
@Test | |||
public void testClientInitialization() throws Exception { | |||
JiraSite site = new JiraSite(new URL("https://nonexistent.url"), null, | |||
"user", "password", | |||
(String)null, |
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.
why cast to String?
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.
Emm, I found this change make the test not functional as expected, I'll fix it :(
updateJiraIssueForAllStatus, groupVisibility, roleVisibility, useHTTPAuth); | ||
} | ||
|
||
@Deprecated |
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.
please specify method to use instead of deprecated one and the reason for deprecation
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.
Deprecate the previous constructor but leave it in place for Java-level compatibility. See https://jenkins.io/doc/developer/plugin-development/pipeline-integration/ , section "Constructor vs. setters"
* | ||
* @author Zhenlei Huang | ||
*/ | ||
public class CredentialsHelper { |
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.
please add tests for this class
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.
OK, I'll do 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.
Tests for CredentialsHelper added. Commit: 0966980
Supports\ Wiki\ notation=Unterst�tzt Wiki-Notation |
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.
Sigh :(
for (StandardUsernamePasswordCredentials c : credentials) { | ||
if (StringUtils.equals(password, Secret.toString(c.getPassword()))) { | ||
cred = c; | ||
break; // found |
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 could return c
here and drop null check below
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.
+1
// HACK, update final fields via reflection, see jls 17.5.3, https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.3 | ||
if (StringUtils.isNotBlank(credentialsId)) { | ||
StandardUsernamePasswordCredentials credentials = CredentialsHelper.lookupSystemCredentials(credentialsId, url); | ||
setCredentials(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.
shouldn't we call setCredentialsId
here as well?
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 is a condition that credentialsId is not blank but no matched credentials. setCredentialsId
should be called under null check with credentials :(
return this; | ||
} | ||
|
||
private void setCredentialsId(String credentialsId) { |
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 would move this method and one below to CredentialsHelper
Ping @artkoshelev |
pom.xml
Outdated
@@ -323,6 +323,12 @@ | |||
</exclusion> | |||
</exclusions> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.powermock</groupId> |
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.
why do we need powermock? (we already have mockito)
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.
Testcase DescriptorImplTest#testValidateConnectionError()
FormValidation validation = descriptor.doValidate("user", "http://localhost:8080", "pass", null, null, false, " ", JiraSite.DEFAULT_TIMEOUT); |
@Test
public void testValidateConnectionError() throws Exception {
when(jiraSession.getMyPermissions()).thenThrow(RestClientException.class);
when(jiraSite.createSession()).thenReturn(jiraSession);
FormValidation validation = descriptor.doValidate("user", "http://localhost:8080", "pass", null, null, false, " ", JiraSite.DEFAULT_TIMEOUT);
assertEquals(validation.kind, FormValidation.Kind.ERROR);
}
The condition passed to descriptor.doValidate() indeed test alternativeURL.
Since mockito does not support mock new operations, I imported powermock to correct this test.
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.
How about using builder pattern for JiraSite
instead? It would help fixing "million-paramaters-constructor" calls for JiraSite
as well as allow properly inject mocked JiraSite
implementation in tests.
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.
Builder pattern seems not helpful with mock, mock JiraSite.createSession()
in this case. Can you explain more about how to mock with builder pattern? Thanks!
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.
In order to properly inject JiraSite
object in DescriptorImpl
you might define getJiraSite()
method and mock it later, but it would require to pass all the parameters you have for JiraSite
constructor. Instead, you define getJiraSiteBuilder()
returning just new builder, then you configure builder in your impl code and call build()
to get actual JiraSite
constructed. Later in tests, you mock getJiraSiteBuilder()
to return mocked/spy'ed builder, which would return mocked site on build()
call.
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'm not sure whether I implement the builder pattern right or not. It looks ugly 👎
I'll include this if it satisfies. See https://github.com/gmshake/jira-plugin/pull/1/files
SystemCredentialsProvider.getInstance().getCredentials().add(newCredentials); | ||
try { | ||
SystemCredentialsProvider.getInstance().save(); | ||
LOGGER.log(Level.INFO, "Migrated 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.
I would prefer to log something more meaningful, e.g. Provided username and password were successfully migrated and stored as {credentialsId}
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.
Good catch:)
|
||
public static void setCredentialsId(JiraSite site, String credentialsId) { | ||
try { | ||
Field f = site.getClass().getDeclaredField("credentialsId"); |
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.
why do we need to use reflection?
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.
JiraSite is designed to use final field credentialsId and credentials, during the progress of deserialization, the instance of JiraSite is created before JiraSite#readResolve() is called,
protected Object readResolve() { |
hence we need use reflection to update relevant fields.
There's another solution, that return a new constructed JiraSite with all need value from the temp deserialization instance from with JiraSite#readResolve(), what do you think?
@artkoshelev Thanks very much for your help with this PR, I did not notice there is an acceptance test for this plugin at the time this PR created, https://github.com/jenkinsci/acceptance-test-harness/blob/master/src/test/java/plugins/JiraPluginTest.java |
I'm not sure how it handled, better to ask someone supporting
|
Any news on this? Need help? |
@tonivdv Sorry but I was busy, I'm planning to get this feature done within next few days :) |
Rebased to latest master, and resolved conflicts. |
It's weird that the coverage decreased :( Related to powermock / jacoco compatibility, see https://github.com/powermock/powermock/wiki/Code-coverage-with-JaCoCo#on-the-fly-instrumentation |
Ping @artkoshelev |
@gmshake sorry, won't be able to review it properly till next week |
Rebased to master and resolved conflicts. |
Ping @artkoshelev |
looking, sorry for the delay |
Really looking forward this merge! Any news on this? |
pom.xml
Outdated
@@ -323,6 +323,12 @@ | |||
</exclusion> | |||
</exclusions> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.powermock</groupId> |
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.
How about using builder pattern for JiraSite
instead? It would help fixing "million-paramaters-constructor" calls for JiraSite
as well as allow properly inject mocked JiraSite
implementation in tests.
@@ -251,10 +289,12 @@ public boolean isAppendChangeTimestamp() { | |||
} | |||
|
|||
protected Object readResolve() { |
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 method is not used as far as i can see, let's delete 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.
We need readResolve()
for backward compatibility, and it is required during deserialization, see https://wiki.jenkins.io/display/JENKINS/Hint+on+retaining+backward+compatibility
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.
Could you please put "ignore unused" annotation with this link?
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.
Sure
private static final Logger LOGGER = Logger.getLogger(CredentialsHelper.class.getName()); | ||
|
||
@CheckForNull | ||
public static StandardUsernamePasswordCredentials lookupSystemCredentials(@CheckForNull String credentialsId, @CheckForNull URL url) { |
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.
Static methods are usually anti-pattern: you couldn't mock them when you test dependent components. Let's implement it as a normal component, say CredentialsManager
?
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.
As the logic is simple enough, only integration tests are written for it. I'm not seeing any value writing unit test here. What do you think?
@@ -1,5 +1,5 @@ | |||
<?jelly escape-by-default='true'?> |
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 would be the user experience when plugin with this changes installed? Would existing username/password be lost?
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.
Existing username/password will migrated to credentials and be stored. The config will not be touched anyway, i.e. require end user re-config jira-plugin global settings. It will be better that no re-config required. I'm struggling on this yet. I'm not sure how to trigger jenkins to auto save jira-plugin settings.
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.
@warden @olamy @oleg-nenashev any ideas on this?
as.grant(Item.READ).onItems(dummy).to("alice"); | ||
r.jenkins.setAuthorizationStrategy(as); | ||
|
||
try (ACLContext context = ACL.as(User.get("admin"))) { |
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.
IDEA suggests to rename unused variable to ignored
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.
👍
JiraSite jiraSite = mock(JiraSite.class); | ||
|
||
JiraSession jiraSession = mock(JiraSession.class); | ||
|
||
@Rule | ||
public final ExpectedException exception = ExpectedException.none(); |
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.
not needed, since we don't expect any exceptions anyway
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.
Gotcha
@samuelmasuy once conflicts fixed and PR comments addressed, i'm happy to merge it |
Rebased to latest master and resolved conflicts. Polished as @artkoshelev suggested. |
Thanks @artkoshelev :) |
Thanks. Will you release a new version soon with this improvement? |
Issue link: https://issues.jenkins-ci.org/browse/JENKINS-45789
TODO:
Better document on credentialsId field.move out from this PRManually mergePartial fix for JENKINS-48357, reverting commit 420f69076b8606c74cf24f301b9446778d88658f #139JENKINS-48357 Fix by bumping version of Jenkins Apache HttpComponents Client 4.x API Plugin #147 into local repository to test this PR.Rebase to latest master.@oleg-nenashev @jglick @warden