Why does my custom step only work in a script block?

I have the Shared Libraries feature setup and working correctly. I also successfully created and used custom steps in a declarative pipeline. However I am having trouble understanding why some type of custom steps only work in a script block.

I have a custom step sayHello:

# vars/sayHello.groovy
def call(List<Map> servers) {    
    servers.each { server ->
        stage("Say Hello on ${server.name}") {
            sshCommand server.ip, "echo Hello from \$(hostname)"
        }
    }
}

In my Jenkinsfile pipeline I have the following:

pipeline {
  	agent any
  	stages {
		stage('Greetings') {
			steps {
				script{
					sayHello(SERVERS)
				}
			}
		}
   	}
}

I also don’t fully understand what I am losing or gaining by using a script block or if there is a better way to achieve what I am doing, making sure I stick to the declarative syntax as much as possible.

As far as I know, in Jenkins, the script block is used to run Groovy code that is not directly supported by the declarative pipeline syntax.
When you use a script block, you are essentially dropping into a scripted pipeline context, which allows you to use more complex Groovy code and logic.

Why Custom Steps Work in script Block
The script block is necessary for certain custom steps because the declarative pipeline syntax has limitations on what Groovy code can be executed directly. :person_shrugging:
The script block allows you to bypass these limitations and run any Groovy code, including custom steps that involve complex logic or iterations.

What You Gain or Lose by Using script Block

  • Gains:
    1. Flexibility: You can use any Groovy code, including complex logic, loops, and conditionals.
    2. Custom Steps: You can call custom steps that are not supported directly in the declarative syntax.
  • Losses:
    1. Declarative Features: You lose some of the declarative pipeline features like automatic stage visualization and certain syntactic checks.
    2. Readability: Mixing declarative and scripted syntax can make the pipeline harder to read and maintain.