Uploaded image for project: 'Jenkins'
  1. Jenkins
  2. JENKINS-58249

Perforce plugin: JCasC cannot export configuration

    • Icon: Bug Bug
    • Resolution: Unresolved
    • Icon: Major Major
    • p4-plugin
    • Jenkins 2.176.1
      Perforce plugin 1.10.0
      JCasC plugin 1.21

      If P4 credentials are configured, then JCasC fails to export Jenkins configuration with an error:

      FAILED TO EXPORT com.cloudbees.plugins.credentials.domains.DomainCredentials#credentials: java.lang.IllegalArgumentException: argument type mismatch
      at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
      at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
      at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
      at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
      at io.jenkins.plugins.casc.impl.configurators.DataBoundConfigurator.describe(DataBoundConfigurator.java:255)
      at io.jenkins.plugins.casc.impl.configurators.HeteroDescribableConfigurator.lambda$convertToNode$de0cd4f8$1(HeteroDescribableConfigurator.java:224)
      at io.vavr.CheckedFunction0.lambda$unchecked$52349c75$1(CheckedFunction0.java:201)
      at io.jenkins.plugins.casc.impl.configurators.HeteroDescribableConfigurator.convertToNode(HeteroDescribableConfigurator.java:224)
      at io.jenkins.plugins.casc.impl.configurators.HeteroDescribableConfigurator.lambda$describe$5(HeteroDescribableConfigurator.java:103)
      at io.vavr.control.Option.map(Option.java:373)
      at io.jenkins.plugins.casc.impl.configurators.HeteroDescribableConfigurator.describe(HeteroDescribableConfigurator.java:103)
      at io.jenkins.plugins.casc.impl.configurators.HeteroDescribableConfigurator.describe(HeteroDescribableConfigurator.java:51)
      at io.jenkins.plugins.casc.Attribute.describe(Attribute.java:194)
      at io.jenkins.plugins.casc.impl.configurators.DataBoundConfigurator.describe(DataBoundConfigurator.java:264)
      at io.jenkins.plugins.casc.Attribute.describe(Attribute.java:194)
      at com.cloudbees.plugins.credentials.casc.SystemCredentialsProviderConfigurator.describe(SystemCredentialsProviderConfigurator.java:76)
      at com.cloudbees.plugins.credentials.casc.SystemCredentialsProviderConfigurator.describe(SystemCredentialsProviderConfigurator.java:48)
      at io.jenkins.plugins.casc.Attribute.describe(Attribute.java:198)
      at com.cloudbees.plugins.credentials.casc.CredentialsRootConfigurator.describe(CredentialsRootConfigurator.java:90)
      at com.cloudbees.plugins.credentials.casc.CredentialsRootConfigurator.describe(CredentialsRootConfigurator.java:52)

          [JENKINS-58249] Perforce plugin: JCasC cannot export configuration

          It is currently not supported at all because of how p4 plugin is written.

          Again it is not a problem with configuration-as-code-plugin but rather a problem with p4 plugin.

          Joseph Petersen (old) added a comment - It is currently not supported at all because of how p4 plugin is written. Again it is not a problem with configuration-as-code-plugin but rather a problem with p4 plugin.

          David Rodriguez added a comment - - edited

          Thanks Joseph,

          @Paul Allen:   This issue was created over 6 months ago.  Configuration-as-code is great solution on configuring Jenkins without the GUI and the ability to configure P4 credential is needed .  Are there plans to resolve this soon?  Otherwise have to find another solution to configure jenkins without the GUI.

          David Rodriguez added a comment - - edited Thanks Joseph, @Paul Allen:   This issue was created over 6 months ago.  Configuration-as-code is great solution on configuring Jenkins without the GUI and the ability to configure P4 credential is needed .  Are there plans to resolve this soon?  Otherwise have to find another solution to configure jenkins without the GUI.

          After some investigation, I've found the root cause of this issue. I guess it's a partial responsibility between the configuration-as-code plugin, and the p4 plugin.

          • Additionally, in that same base class, a method named isSsl is defined. This method returns a boolean (and, importantly, not a TrustImpl object).
          • The method the configuration-as-code plugin uses to describe a data-bound type (as found in DataBoundConfigurator::describe) involves iterating over each argument of a class' constructor (which are assumed to be part of the description), and obtaining their current value on the given instance.
          • Obtaining said valid is done by calling the Attribute::locateGetter method (via Attribute::_getValue, passing it the class definition, and the name of the field to locate. locateGetter proceeds to iterate over all defined methods in the class, looking for a method taking no parameter named after the capitalized field name prefixed by either get or is.
          • In our case, the constructor parameter name is ssl, so locateGetter attempts to find a method named either getSsl or isSsl. Unfortunately for us, P4BaseCredential, the base class of our credential types, defines an isSsl method, as we've seen above. This is the method locateGetter returns, which is then used to obtain the value of our ssl field, which ends up being a boolean (since that's the return type of isSsl). This boolean, in DataBoundConfigurator::describe, gets added to the args array that is then used to construct a new instance of the class, by calling its data-bound constructor.
          • This is where the exception is raised: the type of the 5th element of args is boolean, but the 5th parameter of our constructors is of type TrustImpl. This throws an IllegalArgumentException which ends up being displayed as-is in the configuration export.

          There are two fixes that I can see:

          • First, it seems odd that the configuration-as-code plugin would consider methods with an incompatible return type in Attribute::locateGetter. If that method was provided with the type of the field alongside its name, it could use that information to discard accessors whose name matches one of the two expected patterns, but whose type doesn't match the field. This would've caused locateGetter to return null, and _getValue would've then fallen back to locating a field matching the expected name (note that the comment there is misleading, forceAccess is true so it'll still find private fields), which would've been found. Note that in this execution path too, ExtraFieldUtils.GetField does not check for the field type at all – that might be worth looking at as well. In our specific case, though, the type would've been a match.
          • Second, the p4 plugin should probably consider using the convention around naming getters for data-bound constructors that the configuration-as-code plugin relies on. This would mean replacing the isSsl method with a getSsl one returning a TrustImpl. Note that just adding another valid getter without removing isSsl might or might not work, depending on the order the Java's reflection engine would place them: if isSsl is listed before getSsl is, without the aforementioned change to the configuration-as-code plugin, this bug would still happen.

          Samuel Loretan added a comment - After some investigation, I've found the root cause of this issue. I guess it's a partial responsibility between the configuration-as-code plugin, and the p4 plugin. In the relevant base class of both Perforce credential types ( P4PasswordImpl and P4TicketImpl ), P4BaseCredentials , we find a constructor that takes in its 5th parameter a value named ssl of type TrustImpl . The same is true for the constructor of both concrete types. Additionally, in that same base class, a method named isSsl is defined. This method returns a boolean (and, importantly, not a TrustImpl object). The method the configuration-as-code plugin uses to describe a data-bound type (as found in DataBoundConfigurator::describe ) involves iterating over each argument of a class' constructor (which are assumed to be part of the description), and obtaining their current value on the given instance. Obtaining said valid is done by calling the Attribute::locateGetter method (via Attribute::_getValue , passing it the class definition, and the name of the field to locate. locateGetter proceeds to iterate over all defined methods in the class, looking for a method taking no parameter named after the capitalized field name prefixed by either get or is . In our case, the constructor parameter name is ssl , so locateGetter attempts to find a method named either getSsl or isSsl . Unfortunately for us, P4BaseCredential , the base class of our credential types, defines an isSsl method, as we've seen above. This is the method locateGetter returns, which is then used to obtain the value of our ssl field, which ends up being a boolean (since that's the return type of isSsl ). This boolean , in DataBoundConfigurator::describe , gets added to the args array that is then used to construct a new instance of the class, by calling its data-bound constructor. This is where the exception is raised: the type of the 5th element of args is boolean , but the 5th parameter of our constructors is of type TrustImpl . This throws an IllegalArgumentException which ends up being displayed as-is in the configuration export. There are two fixes that I can see: First, it seems odd that the configuration-as-code plugin would consider methods with an incompatible return type in Attribute::locateGetter . If that method was provided with the type of the field alongside its name, it could use that information to discard accessors whose name matches one of the two expected patterns, but whose type doesn't match the field. This would've caused locateGetter to return null , and _getValue would've then fallen back to locating a field matching the expected name (note that the comment there is misleading, forceAccess is true so it'll still find private fields), which would've been found . Note that in this execution path too, ExtraFieldUtils.GetField does not check for the field type at all – that might be worth looking at as well. In our specific case, though, the type would've been a match. Second, the p4 plugin should probably consider using the convention around naming getters for data-bound constructors that the configuration-as-code plugin relies on. This would mean replacing the isSsl method with a getSsl one returning a TrustImpl . Note that just adding another valid getter without removing isSsl might or might not work, depending on the order the Java's reflection engine would place them: if isSsl is listed before getSsl is, without the aforementioned change to the configuration-as-code plugin, this bug would still happen.

          Joseph Petersen (old) added a comment - - edited

          tynril as JCasC is opinionated and assumes that most plugin follows Jenkins conventions in data binding. Jenkins convention follows Java Bean conventions

          Again I suggest any plugin developer reads https://github.com/jenkinsci/configuration-as-code-plugin/blob/master/docs/PLUGINS.md

          Joseph Petersen (old) added a comment - - edited tynril as JCasC is opinionated and assumes that most plugin follows Jenkins conventions in data binding. Jenkins convention follows Java Bean conventions Again I suggest any plugin developer reads  https://github.com/jenkinsci/configuration-as-code-plugin/blob/master/docs/PLUGINS.md

          However I am not saying JCasC is bug free. If you would like tynril submit a PR that adds a tests and let's see if your suggested change would solve said test.

          Joseph Petersen (old) added a comment - However I am not saying JCasC is bug free. If you would like tynril submit a PR that adds a tests and let's see if your suggested change would solve said test.

          After reading your post and looking at the code, I'd suggest the best fix is located in p4 plugin as it clearly does not follow Java Bean conventions.

          Joseph Petersen (old) added a comment - After reading your post and looking at the code, I'd suggest the best fix is located in p4 plugin as it clearly does not follow Java Bean conventions.

          Paul Allen added a comment - - edited

          Sorry for not looking at this sooner. As suggested I have added a getSsl() method into the Perforce Base Credentials.  I have manually tested this against the configuration-as-code plugin with a Perforce SSL connection and it looks good to me.

          Please let me know if you are able to verify this fix and confirm there are no side effects when using SSL enabled Perforce Servers.

          https://ci.jenkins.io/job/Plugins/job/p4-plugin/job/master/434/

          Paul Allen added a comment - - edited Sorry for not looking at this sooner. As suggested I have added a getSsl() method into the Perforce Base Credentials.  I have manually tested this against the configuration-as-code plugin with a Perforce SSL connection and it looks good to me. Please let me know if you are able to verify this fix and confirm there are no side effects when using SSL enabled Perforce Servers. https://ci.jenkins.io/job/Plugins/job/p4-plugin/job/master/434/

          Hi Paul,

          Would like to test this fix but I need to some help on how to configure the JCasC jenkins.yaml file for the P4 credential.   

          Below  is my attempt to add P4 credential to the jenkins.yaml file  and restart jenkins.  Getting  stacktrace :

          java.lang.IllegalArgumentException: No com.cloudbees.plugins.credentials.Credentials implementation found for P4BaseCredentials"

          // code placeholder
          
          credentials:
            system:
              domainCredentials:
              - credentials:
                - basicSSHUserPrivateKey:
                    description: "Private key credentials for Jenkins GitLab user"
                    id: "gitlab-key"
                    privateKeySource:
                      directEntry:
                        privateKey: ${GITLAB_PRIVATE_KEY}
                    scope: GLOBAL
                    username: ${JENKINS_GITLAB_USER_NAME}
               - P4BaseCredentials:
                    description: "Perforce password credential"
                    id: "p4-userpass"
                    p4port: "${PERFORCE_SERVER}:${PERFORCE_PORT}"
                    scope: GLOBAL
                    username: ${PERFORCE_USER_NAME}
                    password: ${PERFORCE_PASSWORD}

           

           

           

          David Rodriguez added a comment - Hi Paul, Would like to test this fix but I need to some help on how to configure the JCasC jenkins.yaml file for the P4 credential.    Below  is my attempt to add P4 credential to the jenkins.yaml file  and restart jenkins.  Getting  stacktrace : java.lang.IllegalArgumentException: No com.cloudbees.plugins.credentials.Credentials implementation found for P4BaseCredentials" // code placeholder credentials: system: domainCredentials: - credentials: - basicSSHUserPrivateKey: description: "Private key credentials for Jenkins GitLab user" id: "gitlab-key" privateKeySource: directEntry: privateKey: ${GITLAB_PRIVATE_KEY} scope: GLOBAL username: ${JENKINS_GITLAB_USER_NAME} - P4BaseCredentials: description: "Perforce password credential" id: "p4-userpass" p4port: "${PERFORCE_SERVER}:${PERFORCE_PORT}" scope: GLOBAL username: ${PERFORCE_USER_NAME} password: ${PERFORCE_PASSWORD}      

          drodspike – not Paul, but P4BaseCredentials is an abstract base class, you probably want one of its concrete implementation instead (P4PasswordImpl or P4TicketImpl).

          Samuel Loretan added a comment - drodspike – not Paul, but P4BaseCredentials is an abstract base class, you probably want one of its concrete implementation instead ( P4PasswordImpl or P4TicketImpl ).

          Configured the JCasC jenkins.yaml file as follows with the "P4PasswordImpl" credentials  and was successful 

          credentials:
            system:
              domainCredentials:
              - credentials:
                - basicSSHUserPrivateKey:
                    description: "Private key credentials for Jenkins GitLab user"
                    id: "gitlab-key"
                    privateKeySource:
                      directEntry:
                        privateKey: "${GITLAB_PRIVATE_KEY}"
                    scope: GLOBAL
                    username: "${JENKINS_GITLAB_USER_NAME}"
                - P4PasswordImpl: 
                    description: "Perforce password credential"
                    id: "p4-userpass"
                    p4port: "${P4PORT}"
                    scope: GLOBAL
                    username: "${P4USER}"
                    password: "${P4PASSWD}"

          When attempting to view the configuration as code configuration, got an export error by doing:

              Manage jenkins --> Configuration as Code --> Actions: View Configuration

          credentials:
            system:
              domainCredentials:
              - credentials: |-
                  FAILED TO EXPORT
                  com.cloudbees.plugins.credentials.domains.DomainCredentials#credentials: java.lang.IllegalArgumentException: argument type mismatch
                    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
                    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)

          Even though I got the EXPORT error, when trying to view the configuration,  the"P4 credential" in the jenkins.yaml  worked fine in a jenkins job.

           

          David Rodriguez added a comment - Configured the JCasC jenkins.yaml file as follows with the "P4PasswordImpl" credentials  and was successful  credentials: system: domainCredentials: - credentials: - basicSSHUserPrivateKey: description: "Private key credentials for Jenkins GitLab user" id: "gitlab-key" privateKeySource: directEntry: privateKey: "${GITLAB_PRIVATE_KEY}" scope: GLOBAL username: "${JENKINS_GITLAB_USER_NAME}" - P4PasswordImpl: description: "Perforce password credential" id: "p4-userpass" p4port: "${P4PORT}" scope: GLOBAL username: "${P4USER}" password: "${P4PASSWD}" When attempting to view the configuration as code configuration, got an export error by doing:     Manage jenkins --> Configuration as Code --> Actions: View Configuration credentials: system: domainCredentials: - credentials: |- FAILED TO EXPORT com.cloudbees.plugins.credentials.domains.DomainCredentials#credentials: java.lang.IllegalArgumentException: argument type mismatch at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) Even though I got the EXPORT error, when trying to view the configuration,  the"P4 credential" in the jenkins.yaml  worked fine in a jenkins job.  

            p4paul Paul Allen
            courteouselk Anton Bronnikov
            Votes:
            1 Vote for this issue
            Watchers:
            5 Start watching this issue

              Created:
              Updated: