Skip to content
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

Merged
merged 17 commits into from Jul 8, 2018

Conversation

gmshake
Copy link
Contributor

@gmshake gmshake commented Feb 1, 2018

@coveralls
Copy link

coveralls commented Feb 1, 2018

Coverage Status

Coverage increased (+1.08%) to 53.273% when pulling d063bbc on gmshake:JENKINS-45789 into bed1307 on jenkinsci:master.


// 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);
Copy link
Member

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?

Copy link
Contributor Author

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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why cast to String?

Copy link
Contributor Author

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
Copy link
Member

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

Copy link
Contributor Author

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 {
Copy link
Member

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

Copy link
Contributor Author

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 :)

Copy link
Contributor Author

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
Copy link
Contributor Author

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
Copy link
Member

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

Copy link
Contributor Author

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);
Copy link
Member

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?

Copy link
Contributor Author

@gmshake gmshake Apr 23, 2018

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) {
Copy link
Member

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

@gmshake
Copy link
Contributor Author

gmshake commented Apr 29, 2018

Ping @artkoshelev

pom.xml Outdated
@@ -323,6 +323,12 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
Copy link
Member

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)

Copy link
Contributor Author

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);
made false positive IIUC.

    @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.

Copy link
Member

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.

Copy link
Contributor Author

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!

Copy link
Member

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.

Copy link
Contributor Author

@gmshake gmshake Jul 5, 2018

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");
Copy link
Member

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}

Copy link
Contributor Author

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");
Copy link
Member

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?

Copy link
Contributor Author

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?

@gmshake
Copy link
Contributor Author

gmshake commented May 1, 2018

@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
Do I need to update the test, since this PR introduced incompatible UI change ? Then there would be another issue that new version of Jira plugin will break the acceptance test before relevant tests updated. Any suggestion?

@artkoshelev
Copy link
Member

I'm not sure how it handled, better to ask someone supporting acceptance-test-harness. my guess would be:

  1. made PR with fix for acceptance tests, wait for builds to be red
  2. merge this change, release plugin
  3. re-run build on PR above, merge PR for acceptance tests

@tonivdv
Copy link

tonivdv commented Jun 1, 2018

Any news on this? Need help?

@gmshake
Copy link
Contributor Author

gmshake commented Jun 1, 2018

@tonivdv Sorry but I was busy, I'm planning to get this feature done within next few days :)

@gmshake
Copy link
Contributor Author

gmshake commented Jun 3, 2018

Rebased to latest master, and resolved conflicts.

@gmshake
Copy link
Contributor Author

gmshake commented Jun 4, 2018

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

@gmshake
Copy link
Contributor Author

gmshake commented Jun 5, 2018

Ping @artkoshelev

@artkoshelev
Copy link
Member

@gmshake sorry, won't be able to review it properly till next week

@gmshake
Copy link
Contributor Author

gmshake commented Jun 17, 2018

Rebased to master and resolved conflicts.

@gmshake
Copy link
Contributor Author

gmshake commented Jul 2, 2018

Ping @artkoshelev

@artkoshelev
Copy link
Member

looking, sorry for the delay

@samuelmasuy
Copy link

Really looking forward this merge! Any news on this?

pom.xml Outdated
@@ -323,6 +323,12 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
Copy link
Member

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() {
Copy link
Member

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

Copy link
Contributor Author

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

Copy link
Member

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?

Copy link
Contributor Author

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) {
Copy link
Member

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?

Copy link
Contributor Author

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'?>
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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"))) {
Copy link
Member

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

Copy link
Contributor Author

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();
Copy link
Member

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha

@artkoshelev
Copy link
Member

@samuelmasuy once conflicts fixed and PR comments addressed, i'm happy to merge it

@gmshake
Copy link
Contributor Author

gmshake commented Jul 5, 2018

Rebased to latest master and resolved conflicts. Polished as @artkoshelev suggested.

@artkoshelev artkoshelev merged commit 0beb22c into jenkinsci:master Jul 8, 2018
@gmshake
Copy link
Contributor Author

gmshake commented Jul 9, 2018

Thanks @artkoshelev :)

@tonivdv
Copy link

tonivdv commented Jul 9, 2018

Thanks. Will you release a new version soon with this improvement?

@artkoshelev
Copy link
Member

@tonivdv please ask @olamy or @warden to release new version =)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants