Jenkinsfile - how to set and use variables with the same name?

Viewed 89

I think this is a variable scoping question.

I have a Jenkins pipeline like this with a repeated switch-case statement. My real use-case has about 10 sites and a lot of different variables that are set, and several Jenkins jobs could use it, so I'd like to pull out that logic into a reusable function / class.

pipeline {
  agent any
  parameters {
    choice (
      name: 'SOURCE_ENV',
      choices: [
        'site_01'
        'site_02'
      ],
      description: 'The source environment you want to copy to another server.',
    )
    choice (
      name: 'TARGET_ENV',
      choices: [
        'site_01'
        'site_02'
      ],
      description: 'The environment you want to refresh from another server.',
    )
  } // end params
  stages {
    stage('Initialization') {
      steps {
        script {
            switch (SOURCE_ENV) {
                case 'site_01':
                    def SOURCE_WEB_SERVER =     'site_01.someplace.org'
                    def SOURCE_DB_SERVER =      'site_01.db.someplace.org'
                    def SOURCE_DB_NAME =        'site_01_db'
                    def SOURCE_SITE_URL =       'https://some-spiffy-name.org'
                    break
                case 'site_02':
                    def SOURCE_WEB_SERVER =     'site_02.someplace.org'
                    def SOURCE_DB_SERVER =      'site_02.db.someplace.org'
                    def SOURCE_DB_NAME =        'site_02_db'
                    def SOURCE_SITE_URL =       'https://some-different-name.org'
                    break
                default:
                    error("ERROR: Unknown environment ${SOURCE_ENV}.")
                    break
            }
            switch (TARGET_ENV) {
                case 'site_01':
                    def TARGET_WEB_SERVER =     'site_01.someplace.org'
                    def TARGET_DB_SERVER =      'site_01.db.someplace.org'
                    def TARGET_DB_NAME =        'site_01_db'
                    def TARGET_SITE_URL =       'https://some-spiffy-name.org'
                    break
                case 'site_02':
                    def TARGET_WEB_SERVER =     'site_02.someplace.org'
                    def TARGET_DB_SERVER =      'site_02.db.someplace.org'
                    def TARGET_DB_NAME =        'site_02_db'
                    def TARGET_SITE_URL =       'https://some-different-name.org'
                    break
                default:
                    error("ERROR: Unknown environment ${TARGET_ENV}.")
                    break
            }
        } // end script
      } // end steps
    } // end stage

    stage('Do the Thing') {
      steps {
          script {
            echo "Do the thing we want to do."
          }
      } // end steps
    } // end stage
  } // end stages
} // end pipeline

I have moved the logic into a new file and it loads, but it errors and doesn't work how I wanted.

Excerpt from the Jenkinsfile:

...
    stage('Initialization') {
      steps {
        script {
          def source = load 'website_environment_ops/setEnvironments.groovy'
          source.setEnvironments(params.SOURCE_ENV)
          def target = load 'website_environment_ops/setEnvironments.groovy'
          target.setEnvironments(params.TARGET_ENV)

          echo "SOURCE WEB_SERVER:  ${source.WEB_SERVER}\n"
          echo "TARGET WEB_SERVER:  ${target.WEB_SERVER}\n"
        } // end script
      } // end steps
    } // end stage
...

The setEnvironments.groovy file.

def setEnvironments(ENV) {
 switch (ENV) {
  case 'site_01':
   def WEB_SERVER =  'site_01.someplace.org'
   def DB_SERVER =  'site_01.db.someplace.org'
   def DB_NAME =    'site_01_db'
   def SITE_URL =   'https://some-spiffy-name.org'
   break
  case 'site_02':
   def WEB_SERVER =  'site_02.someplace.org'
   def DB_SERVER =  'site_02.db.someplace.org'
   def DB_NAME =    'site_02_db'
   def SITE_URL =   'https://some-different-name.org'
   break
  default:
   error("ERROR: Unknown environment.")
   break
 }
}
return this

I am getting errors like this: 'Script1.groovy: 50: The current scope already contains a variable of the name WEB_SERVER'

I understand this is because my variables are not actually scoped into 'source' and 'target' as I would have hoped, and are being loaded into the same scope.

However, I don't know how to get variables of the same name scoped to different entities that can be used within one Jenkinsfile. Or, I don't know how to dynamically name these variables. Or, I don't know how to use a solution like setEnvironments.groovy file to reduce duplication in my code.

I'd like to be able to call variables like source.WEB_SERVER vs target.WEB_SERVER, or use setEnvironments.groovy to name its variables dynamically like SOURCE_WEB_SERVER vs TARGET_WEB_SERVER.

Does anyone have any ideas? Thx

1 Answers

Try restructuring your setEnvironments.groovy script like below. Basically, you shouldn't be declaring variables each time you are setting them.

def WEB_SERVER
def DB_SERVER
def DB_NAME
def SITE_URL

def setEnvironments(ENV) {
   switch (ENV) {
   case 'site_01':
      WEB_SERVER =  'site_01.someplace.org'
      DB_SERVER =  'site_01.db.someplace.org'
      DB_NAME =    'site_01_db'
      SITE_URL =   'https://some-spiffy-name.org'
   case 'site_02':
      WEB_SERVER =  'site_02.someplace.org'
      DB_SERVER =  'site_02.db.someplace.org'
      DB_NAME =    'site_02_db'
      SITE_URL =   'https://some-different-name.org'
   default:
      error("ERROR: Unknown environment.")
   }
}

return this

Then use it like below.

def params = load 'website_environment_ops/setEnvironments.groovy'
params.setEnvironments(params.SOURCE_ENV)
echo "$params.WEB_SERVER"

params.setEnvironments(params.TARGET_ENV)
echo "$params.WEB_SERVER"

On a side note, it would be best if you considered moving your pipeline to a Shared Library. This will allow you to separate out your reusable code and manage them as proper Groovy code. Here is an example (This is a bit complex, but would give you an idea) for your requirement to manage properties. Check here and here.

Update: Returning a Map of Properties

If you really want to keep both target and source properties at the same time you can return a map of properties from your function. Refer to the below.

def setEnvironments(ENV) {
    def propMap = [:]
    switch (ENV) {
      case 'site_01':
        propMap["WEB_SERVER"]   =  'site_01.someplace.org'
        propMap["DB_SERVER"]    =  'site_01.db.someplace.org'
        propMap["DB_NAME"]      =  'site_01_db'
        propMap["SITE_URL"]     =  'https://some-spiffy-name.org'
      case 'site_02':
        propMap["WEB_SERVER"]  =  'site_02.someplace.org'
        propMap["DB_SERVER"]   =  'site_02.db.someplace.org'
        propMap["DB_NAME"]     =  'site_02_db'
        propMap["SITE_URL"]   =  'https://some-different-name.org'
      default:
         error("ERROR: Unknown environment.")
    }
    return propMap
 }

In Pipeline

def script = load 'website_environment_ops/setEnvironments.groovy'
def source = script.setEnvironments(params.SOURCE_ENV)

def target = script.setEnvironments(params.TARGET_ENV)
            
echo source["WEB_SERVER"]
echo target["WEB_SERVER"]
echo source["DB_SERVER"]
echo target["DB_SERVER"]
Related