Jenkins Credentials Manager + Active Choices

Oct 07 2020

Voglio accedere alle credenziali Jenkins per autenticarmi con Git prima dello script della pipeline, per riempire il parametro Active Choices.

Nella pipeline lo faccio semplicemente con withCredentials. Ma durante la fase di pre-pipeline (cioè il parametro Active Choices) le estensioni Hudson come withCredentialso sshAgentnon sono accessibili. Attualmente git ls-remote -h [email protected]:repo.giteseguo groovy con chiave non crittografata nella directory jenkins_home (che non è una buona idea).

So che c'è qualche possibilità di importare e costruire oggetti Java leggendo il codice sorgente di Jenkin e dei suoi plugin, ma questo sembra infinito.

Quindi: è possibile in qualche modo importare e applicare facilmente le estensioni Hudson allo script corrente di Groovy o qualcuno ha una soluzione pronta per questo (apparentemente nessuno ha pubblicato nulla su questo caso)?

Risposte

jayhendren Oct 08 2020 at 03:41

Mi sembra che tu ne sia già consapevole, ma ci sono un paio di modi semplici per farlo e un paio di modi difficili per farlo.

I modi semplici:

  • Rendi disponibile il tuo repository per il checkout senza chiavi SSH o credenziali di autenticazione.
  • Aggiungi manualmente una coppia di chiavi SSH per il checkout del repo all'utente Jenkins sul tuo nodo master Jenkins (poiché gli script Active Choice Groovy vengono eseguiti sul nodo master) (ovvero non utilizzare l'archivio delle credenziali).

I modi più difficili:

  • Utilizza l'API Java Jenkins nel codice Active Choices per estrarre le credenziali dall'archivio delle credenziali. I documenti API sono disponibili per l'archivio delle credenziali e il tipo di credenziali ssh . Non c'è documentazione su come utilizzare le classi Java disponibili in questi plugin, quindi spetta a te capirlo da solo.
  • Creare un "meta" lavoro secondario per aggiornare i parametri nel lavoro Jenkins originale (inclusi gli script Active Choices) - lo descrivo un po 'di seguito.

Se non è possibile eseguire nessuna delle soluzioni semplici per motivi di sicurezza, non esiste un modo semplice e diretto per aggiornare i parametri di Active Choice in base al contenuto di un repository git. Questo è perché:

  1. Come hai detto, "durante la fase di pre-pipeline (cioè il parametro Active Choices) le estensioni di Hudson come withCredentials o sshAgent non sono accessibili".
  2. La configurazione dei parametri, inclusi i parametri Active Choice, è presa dall'esecuzione precedente di quella pipeline, non da quella corrente. Ciò significa che, per aggiornare i parametri dall'interno del codice della pipeline, dovrai eseguire la pipeline, il che vanifica il punto di ciò che vuoi fare.

C'è una soluzione per 1., che consiste nell'usare Scripted Pipelines invece di Dichiarative, ma questo non aggira i problemi di 2.

La soluzione che ho trovato quando mi sono imbattuto in questo problema è creare un secondo lavoro Jenkins (io lo chiamo un "meta lavoro") che aggiorna i parametri del lavoro originale, inclusi gli script Active Choices. Questo secondo meta job viene attivato da un git hook sul repository che utilizzo per riempire le opzioni nei miei parametri Active Choices. Questo ha il vantaggio che non è necessario eseguire la pipeline del lavoro originale per aggiornarne i parametri, risolvendo i problemi in 2. Inoltre, poiché è ancora un lavoro della pipeline, è possibile utilizzare i normali passaggi della pipeline, consentendo così di accedere al archivio delle credenziali.

Il mio lavoro sembra (più o meno) così: l'ho semplificato molto per adattarlo qui, quindi dovrai fare un po 'di modifiche per farlo funzionare nel modo desiderato. Probabilmente ci sono un sacco di errori e bug in questo perché ho strappato uno script esistente per farlo. Alcune cose da notare:

  • Questo fa un uso massiccio delle API di Jenkins, quindi dovrai disabilitare la sandbox degli script o inserire nella whitelist un sacco di funzioni nel gestore della sicurezza di Jenkins per farlo funzionare
  • Questa è ancora tecnicamente una pipeline con script anche se sembra molto più simile a un normale script Groovy che a un file pipeline
  • Poiché si tratta di una pipeline, è possibile utilizzare passaggi come il withCredentialscheck out dei repository utilizzando l'archivio delle credenziali.
import groovy.json.JsonOutput

import org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript 

import org.biouno.unochoice.*
import org.biouno.unochoice.model.*

def metadataUrl = 'ssh://git@**************/metadata.git'
def metadataBranch = 'master'

def metadata = [:]

node('master') {
  git(
    url: metadataUrl,
    branch: metadataBranch,
    credentialsId: 'jenkins',
    changelog: false,
  )

  def dataDir = "${pwd()}/myJsonFiles" // can't use File.eachFileMatch() due to: // https://wiki.jenkins.io/display/JENKINS/Pipeline+CPS+method+mismatches def files = new File(dataDir).list().findAll() { f-> FilenameUtils.getExtension(f) == 'json' } files.each() { f-> // TODO: nice error messages if files are misformatted def name = FilenameUtils.removeExtension(f) def data = readJson(file: "${dataDir}/${f}") metadata[name] = data } } // ChoiceParameterDefinition is the class name for the built-in static choice parameter. // CascadeChoiceParameter is the class name for the Active Choices dynamic choice parameter. // begin first parameter def fileParam = new ChoiceParameterDefinition( 'file', 'The JSON file to use', ) fileParam.setChoices(metadata.keySet()) // end first parameter // begin second parameter def metadataJson = JsonOutput.toJson(metadata) def scriptText = """ import groovy.json.JsonSlurper def slurper = new JsonSlurper() def metadata = slurper.parseText('${metadataJson}')

return metadata[file]['versions']
"""
def mainScript = new SecureGroovyScript(scriptText, true)
def fallbackScript = new SecureGroovyScript('return ["Error encountered - see logs"]', true)
def activeChoiceScript = new GroovyScript(mainScript, fallbackScript)

def versionParam = new CascadeChoiceParameter(
  'version', 
  'The version to use',
  activeChoiceScript,
  'PT_SINGLE_SELECT',
  'file',
  false,
)
// end second parameter


def paramProp = new ParametersDefinitionProperty(fileParam, versionParam)


// this is the full name of the actual job which needs dynamic parameters
def job = Jenkins.instance.getItemByFullName('UNIX/deploy_to_environment/master')


// There is no "setProperty" - we need to remove and replace.
// removeProperty is safe to run even if no matching properties are set on the job.
// However, this method only removes one property per method call.
// Theoretically, this means the job could accumulate multiple conflicting
// ParametersDefinitionProperty properties.
// TODO: run removeProperty in a while loop to eliminate all matching properties for extra safety.
job.removeProperty(ParametersDefinitionProperty)

job.addProperty(paramProp)