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

Unable to create folder credentials for plain-credentials-plugin

    • Icon: Bug Bug
    • Resolution: Unresolved
    • Icon: Minor Minor
    • None
    • Jenkins ver. 2.202
      Plain Credentials Plugin 1.5
      Job DSL 1.76

      Trying to create folder using job DSL but there is no option for credentials from plain-credentials-plugin - FileCredential and StringCredentials. Only credentials from credentials plugin are available.

          [JENKINS-59971] Unable to create folder credentials for plain-credentials-plugin

          Jesse Rittner added a comment -

          I was able to work around this bug using the configure option for string creds.

           

          properties {
            folderCredentialsProperty {
              domainCredentials {
                 domainCredentials {
                  domain {
                    name('')
                    description('')
                  }
                }
              }   
            }
          }
          configure { folder ->
               def configNode = folder / 'properties''com.cloudbees.hudson.plugins.folder.properties.FolderCredentialsProvider_-FolderCredentialsProperty''domainCredentialsMap' / 'entry' / 'java.util.concurrent.CopyOnWriteArrayList'
             configNode << 'org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl'(plugin: 'plain-credentials@1.5') {
               id('secret-id')
               description('secret desc')
               secret(hudson.util.Secret.fromString('secret').getEncryptedValue())
             }
          }
          

          Unfortunately, I still cannot get file creds to work properly. It requires using com.cloudbees.plugins.credentials.SecretBytes, but attempting to do so gives all sorts of bizarre errors, like unable to resolve class com.cloudbees.plugins.credentials.SecretBytes or No such property: cloudbees for class: java.lang.String.

           

          Jesse Rittner added a comment - I was able to work around this bug using the configure  option for string creds.   properties { folderCredentialsProperty { domainCredentials { domainCredentials { domain { name('') description('') } } }   } } configure { folder ->      def configNode = folder / 'properties' /  'com.cloudbees.hudson.plugins.folder.properties.FolderCredentialsProvider_-FolderCredentialsProperty' /  'domainCredentialsMap' / 'entry' / 'java.util.concurrent.CopyOnWriteArrayList'   configNode << 'org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl' (plugin: 'plain-credentials@1.5' ) { id( 'secret-id' ) description( 'secret desc' ) secret(hudson.util.Secret.fromString( 'secret' ).getEncryptedValue())   } } Unfortunately, I still cannot get file creds to work properly. It requires using com.cloudbees.plugins.credentials.SecretBytes, but attempting to do so gives all sorts of bizarre errors, like unable to resolve class com.cloudbees.plugins.credentials.SecretBytes or No such property: cloudbees for class: java.lang.String .  

          Jason Swager added a comment - - edited

          rittneje - perhaps this will help. Your original example helped me get much further; I couldn't have figured out the secret file stuff without your example.

          properties {
            folderCredentialsProperty {
              domainCredentials {
                 domainCredentials {
                  domain {
                    name('')
                    description('')
                  }
                }
              }   
            }
          }
          configure { folder ->
               def configNode = folder / 'properties''com.cloudbees.hudson.plugins.folder.properties.FolderCredentialsProvider_-FolderCredentialsProperty''domainCredentialsMap' / 'entry' / 'java.util.concurrent.CopyOnWriteArrayList'
            configNode << 'org.jenkinsci.plugins.plaincredentials.impl.FileCredentialsImpl' {
              id('secret-file-id')
              description('secret file desc')
              fileName('test.txt')
              secretBytes(com.cloudbees.plugins.credentials.SecretBytes.fromBytes('''This is a multiline file
          Line two
          final line'''.getBytes()).toString())
            }
          }
          

          This example won't work with a binary file.  If that was necessary, you could generate a BASE64 encode of the binary file, put that in place of the string and do a decodeBase64() on it to get the bytes.

          Jason Swager added a comment - - edited rittneje - perhaps this will help. Your original example helped me get much further; I couldn't have figured out the secret file stuff without your example. properties { folderCredentialsProperty { domainCredentials { domainCredentials { domain { name('') description('') } } }   } } configure { folder ->      def configNode = folder / 'properties' /  'com.cloudbees.hudson.plugins.folder.properties.FolderCredentialsProvider_-FolderCredentialsProperty' /  'domainCredentialsMap' / 'entry' / 'java.util.concurrent.CopyOnWriteArrayList' configNode << 'org.jenkinsci.plugins.plaincredentials.impl.FileCredentialsImpl' { id( 'secret-file-id' ) description( 'secret file desc' ) fileName( 'test.txt' ) secretBytes(com.cloudbees.plugins.credentials.SecretBytes.fromBytes('''This is a multiline file Line two final line'''.getBytes()).toString()) } } This example won't work with a binary file.  If that was necessary, you could generate a BASE64 encode of the binary file, put that in place of the string and do a decodeBase64() on it to get the bytes.

          Jesse Rittner added a comment -

          Thanks, but unfortunately that still gives those weird error messages I posted.

          Also, just FYI you should also set scope('GLOBAL') for your credentials. Otherwise, your config might be corrupted.

           

          Jesse Rittner added a comment - Thanks, but unfortunately that still gives those weird error messages I posted. Also, just FYI you should also set scope('GLOBAL') for your credentials. Otherwise, your config might be corrupted.  

          Hello,
          daspilker what makes plain-credentials-plugin incompatible with job-dsl-plugin ?
          What would be needed to provide a fix for this ?

          Krystian Marek added a comment - Hello, daspilker what makes plain-credentials-plugin incompatible with job-dsl-plugin ? What would be needed to provide a fix for this ?

          Riccardo Boettcher added a comment - - edited

          Hi Daniel Spilker,

          I have tried to find a solution for the issue. Please have a look here https://github.com/RiccardoBoettcher/plain-credentials-plugin/tree/JENKINS-59971

          As I'm not experienced with the Jenkins plugin development yet, I don't know what is the best strategy to introduce a breaking change. May be someone can assist me with that?

           

          As visible in the screenshot, the change makes a basicStringCredential (a functional equivalent implementation of the existing StringCredentialImpl) available in the JobDSL API browser.

          But, this draft has still some missing parts:

          • when creating new credentials via the WebUI it can not be distinguished from the existing implementation for "secret text"
          • when selecting it as the type of a new credentials no properties are shown

          Riccardo Boettcher added a comment - - edited Hi Daniel Spilker, I have tried to find a solution for the issue. Please have a look here https://github.com/RiccardoBoettcher/plain-credentials-plugin/tree/JENKINS-59971 As I'm not experienced with the Jenkins plugin development yet, I don't know what is the best strategy to introduce a breaking change. May be someone can assist me with that?   As visible in the screenshot, the change makes a basicStringCredential (a functional equivalent implementation of the existing StringCredentialImpl) available in the JobDSL API browser. But, this draft has still some missing parts: when creating new credentials via the WebUI it can not be distinguished from the existing implementation for "secret text" when selecting it as the type of a new credentials no properties are shown

          Dmitry added a comment - - edited

          Hello rittneje

          I have implemented a workaround for FileCredentialsImpl.

          Created shared lib method (vars/stringToSecretBytes.groovy)

          import com.cloudbees.plugins.credentials.SecretBytes
          def call(s) {
              return SecretBytes.fromString(s.bytes.encodeBase64().toString())
          }
          

           

          in repo for job dsl:

          ./Jenkinsfile
          ./secrets.yaml
          ./seed.groovy

           secrets.yaml

          - id: secretFile
            secret: |
              secret text file
            type: file
            fileName: secret.txt
            description: secretFile
          - id: secretText
            secret: 'secret text'
            type: text
            description: secretText
          

          Jenkinsfile:

          node {
              stage('seed') {
                  checkout scm
          
                  def secrets = readYaml file: 'secrets.yaml'
                  def i = 0
                  for (s in secrets) {
                      if (s.type == 'file') {
                          secrets[i].secret = stringToSecretBytes(s.secret).toString() //stringToSecretBytes - shared lib method, wrapper for 
                                                                                       // com.cloudbees.plugins.credentials.SecretBytes.fromString
                      }
                      i += 1
                  }
                  // store secrets in secrets.json where secrets of type 'file' have been converted into com.cloudbees.plugins.credentials.SecretBytes strings
                  writeJSON file: 'secrets.json', json: secrets
                  jobDsl targets: 'seed.groovy', removedJobAction: 'DELETE'
              }
          }
          

          seed.groovy:

          def secrets = new groovy.json.JsonSlurper().parseText(readFileFromWorkspace('secrets.json'))
          
          folder('folderName') {
              properties {
                  folderCredentialsProperty {
                      domainCredentials {
                          domainCredentials {
                              domain {
                                  name('')
                                  description('')
                              }
                          }
                      }
                  }
              }
              // secrets
              if (secrets.any{it.type == 'file' || it.type == 'text'}) {
                  configure { folder ->
                      def configNode = folder / 'properties' /  'com.cloudbees.hudson.plugins.folder.properties.FolderCredentialsProvider_-FolderCredentialsProperty' /  'domainCredentialsMap' / 'entry' / 'java.util.concurrent.CopyOnWriteArrayList'
                      for (c in secrets) {
                          if (c.type == 'file') {
                              configNode << 'org.jenkinsci.plugins.plaincredentials.impl.FileCredentialsImpl' {
                                  id(c.id)
                                  description(c.description)
                                  fileName(c.fileName)
                                  secretBytes(c.secret)
                              }
                          }
                          if (c.type == 'text') {
                              configNode << 'org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl' {
                                  id(c.id)
                                  description(c.description)
                                  secret(hudson.util.Secret.fromString(c.secret).getEncryptedValue())
                              }
                          }
                      }
                  }
              }
          }
          

           

          It works for me:

          The secret with the file is correct. For example:

          node {
              withCredentials([file(credentialsId: 'secretFile', variable: 'TEST')]) {
                  sh "cat ${TEST}"
              }
          }
          

          This pipeline printed:

          15:30:39  [Pipeline] {
          15:30:39  [Pipeline] withCredentials
          15:30:39  Masking supported pattern matches of $TEST
          15:30:39  [Pipeline] {
          15:30:39  [Pipeline] sh
          15:30:39  Warning: A secret was passed to "sh" using Groovy String interpolation, which is insecure.
          15:30:39  		 Affected argument(s) used the following variable(s): [TEST]
          15:30:39  		 See https://jenkins.io/redirect/groovy-string-interpolation for details.
          15:30:40  + cat ****
          15:30:40  secret text file
          15:30:40  [Pipeline] }
          15:30:41  [Pipeline] // withCredentials
          15:30:41  [Pipeline] }
          15:30:41  [Pipeline] // node
          15:30:41  [Pipeline] End of Pipeline
          15:30:41  Finished: SUCCESS
          

          "secret text file" from secrets (in secrets.yaml)

           

          secrets.yaml I used for storage convenience (for example encrypt with mozilla sops), you can rework the example for your cases

           

          Dmitry added a comment - - edited Hello rittneje I have implemented a workaround for FileCredentialsImpl. Created shared lib method (vars/stringToSecretBytes.groovy) import com.cloudbees.plugins.credentials.SecretBytes def call(s) { return SecretBytes.fromString(s.bytes.encodeBase64().toString()) }   in repo for job dsl: ./Jenkinsfile ./secrets.yaml ./seed.groovy  secrets.yaml - id: secretFile secret: | secret text file type: file fileName: secret.txt description: secretFile - id: secretText secret: 'secret text' type: text description: secretText Jenkinsfile: node { stage( 'seed' ) { checkout scm def secrets = readYaml file: 'secrets.yaml' def i = 0 for (s in secrets) { if (s.type == 'file' ) { secrets[i].secret = stringToSecretBytes(s.secret).toString() //stringToSecretBytes - shared lib method, wrapper for // com.cloudbees.plugins.credentials.SecretBytes.fromString } i += 1 } // store secrets in secrets.json where secrets of type 'file' have been converted into com.cloudbees.plugins.credentials.SecretBytes strings writeJSON file: 'secrets.json' , json: secrets jobDsl targets: 'seed.groovy' , removedJobAction: 'DELETE' } } seed.groovy: def secrets = new groovy.json.JsonSlurper().parseText(readFileFromWorkspace( 'secrets.json' )) folder( 'folderName' ) { properties { folderCredentialsProperty { domainCredentials { domainCredentials { domain { name('') description('') } } } } } // secrets if (secrets.any{it.type == 'file' || it.type == 'text' }) { configure { folder -> def configNode = folder / 'properties' / 'com.cloudbees.hudson.plugins.folder.properties.FolderCredentialsProvider_-FolderCredentialsProperty' / 'domainCredentialsMap' / 'entry' / 'java.util.concurrent.CopyOnWriteArrayList' for (c in secrets) { if (c.type == 'file' ) { configNode << 'org.jenkinsci.plugins.plaincredentials.impl.FileCredentialsImpl' { id(c.id) description(c.description) fileName(c.fileName) secretBytes(c.secret) } } if (c.type == 'text' ) { configNode << 'org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl' { id(c.id) description(c.description) secret(hudson.util.Secret.fromString(c.secret).getEncryptedValue()) } } } } } }   It works for me: The secret with the file is correct. For example: node { withCredentials([file(credentialsId: 'secretFile' , variable: 'TEST' )]) { sh "cat ${TEST}" } } This pipeline printed: 15:30:39 [Pipeline] { 15:30:39 [Pipeline] withCredentials 15:30:39 Masking supported pattern matches of $TEST 15:30:39 [Pipeline] { 15:30:39 [Pipeline] sh 15:30:39 Warning: A secret was passed to "sh" using Groovy String interpolation, which is insecure. 15:30:39 Affected argument(s) used the following variable(s): [TEST] 15:30:39 See https: //jenkins.io/redirect/groovy-string-interpolation for details. 15:30:40 + cat **** 15:30:40 secret text file 15:30:40 [Pipeline] } 15:30:41 [Pipeline] // withCredentials 15:30:41 [Pipeline] } 15:30:41 [Pipeline] // node 15:30:41 [Pipeline] End of Pipeline 15:30:41 Finished: SUCCESS "secret text file" from secrets (in secrets.yaml)   secrets.yaml I used for storage convenience (for example encrypt with mozilla sops), you can rework the example for your cases  

          Hallo identw, the proposed workaround looks usable for me. I was currently not any longer actively working on jenkins related issues, that's I took so long to respond.
          I would recommend, if somehow possible, that your workaround can be added to the plugin documentation so that it becomes better visible for users.

          Riccardo Boettcher added a comment - Hallo identw , the proposed workaround looks usable for me. I was currently not any longer actively working on jenkins related issues, that's I took so long to respond. I would recommend, if somehow possible, that your workaround can be added to the plugin documentation so that it becomes better visible for users.

          Jamie Tanna added a comment -

          Now JENKINS-57435 is resolved, it may be possible to do this without a workaround.

          Jamie Tanna added a comment - Now JENKINS-57435 is resolved, it may be possible to do this without a workaround.

            jamietanna Jamie Tanna
            mkovarik Michal Kovarik
            Votes:
            7 Vote for this issue
            Watchers:
            12 Start watching this issue

              Created:
              Updated: