开发者问题收集

groovy.lang.MissingPropertyException:没有这样的属性:sh 类:groovy.lang.Binding

2020-12-21
10762

我尝试在 Jenkins 上构建一个管道,该管道在节点上运行命令并通知我以下错误:

groovy.lang.MissingPropertyException: No such property: sh for class: groovy.lang.Binding
    at groovy.lang.Binding.getVariable(Binding.java:63)
    at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.onGetProperty(SandboxInterceptor.java:270)
    at org.kohsuke.groovy.sandbox.impl.Checker$7.call(Checker.java:353)
    at org.kohsuke.groovy.sandbox.impl.Checker.checkedGetProperty(Checker.java:357)
    at org.kohsuke.groovy.sandbox.impl.Checker.checkedGetProperty(Checker.java:333)
    at org.kohsuke.groovy.sandbox.impl.Checker.checkedGetProperty(Checker.java:333)
    at org.kohsuke.groovy.sandbox.impl.Checker.checkedGetProperty(Checker.java:333)
    at com.cloudbees.groovy.cps.sandbox.SandboxInvoker.getProperty(SandboxInvoker.java:29)
    at com.cloudbees.groovy.cps.impl.PropertyAccessBlock.rawGet(PropertyAccessBlock.java:20)
    at WorkflowScript.run(WorkflowScript:57)
    at WorkflowScript.withGheStatusSender(WorkflowScript:150)
    at WorkflowScript.run(WorkflowScript:56)
    at WorkflowScript.withSlackNotifier(WorkflowScript:178)
    at WorkflowScript.run(WorkflowScript:23)
    at ___cps.transform___(Native Method)
    at com.cloudbees.groovy.cps.impl.PropertyishBlock$ContinuationImpl.get(PropertyishBlock.java:74)
    at com.cloudbees.groovy.cps.LValueBlock$GetAdapter.receive(LValueBlock.java:30)
    at com.cloudbees.groovy.cps.impl.PropertyishBlock$ContinuationImpl.fixName(PropertyishBlock.java:66)
    at sun.reflect.GeneratedMethodAccessor560.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.cloudbees.groovy.cps.impl.ContinuationPtr$ContinuationImpl.receive(ContinuationPtr.java:72)
    at com.cloudbees.groovy.cps.impl.ConstantBlock.eval(ConstantBlock.java:21)
    at com.cloudbees.groovy.cps.Next.step(Next.java:83)
    at com.cloudbees.groovy.cps.Continuable$1.call(Continuable.java:174)
    at com.cloudbees.groovy.cps.Continuable$1.call(Continuable.java:163)
    at org.codehaus.groovy.runtime.GroovyCategorySupport$ThreadCategoryInfo.use(GroovyCategorySupport.java:129)
    at org.codehaus.groovy.runtime.GroovyCategorySupport.use(GroovyCategorySupport.java:268)
    at com.cloudbees.groovy.cps.Continuable.run0(Continuable.java:163)
    at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.access$001(SandboxContinuable.java:18)
    at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.run0(SandboxContinuable.java:51)
    at org.jenkinsci.plugins.workflow.cps.CpsThread.runNextChunk(CpsThread.java:185)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.run(CpsThreadGroup.java:400)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.access$400(CpsThreadGroup.java:96)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:312)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:276)
    at org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService$2.call(CpsVmExecutorService.java:67)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at hudson.remoting.SingleLaneExecutorService$1.run(SingleLaneExecutorService.java:136)
    at jenkins.util.ContextResettingExecutorService$1.run(ContextResettingExecutorService.java:28)
    at jenkins.security.ImpersonatingExecutorService$1.run(ImpersonatingExecutorService.java:59)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
Finished: FAILURE

我引用的 Jenkinsfile 源代码来自此 https://github.com/nobuoka/jenkins-pipeline-sample-for-android/blob/master/Jenkinsfile 。管道成功直到 Assemble 阶段之前

Jenkinsfile:

    stage 'Assemble'
        withGheStatusSender('Assemble', env.GIT_COMMIT, 'Building') {
           sudo sh './gradlew assemble'
        }

        stage 'Lint'
        withGheStatusSender('Lint', env.GIT_COMMIT, 'Checking') {
            // If you run the lint task, lint will be run for all variants, but with only one output.
            // For the time being, specify each variant and lint it.
            def pfs = ['']
            def bts = ['release', 'debug']
            List<String> variants = []
            pfs.each { pf -> bts.each{ bt -> variants.add(pf + (pf.isEmpty() ? bt : bt.capitalize())) } }

            // I want to use the List # collect method, but it doesn't work on Pipeline due to a bug in the groovy-cps library.
            // See : https://issues.jenkins-ci.org/browse/JENKINS-26481
            List<GString> gradleTasks = []
            List<GString> outputFiles = []
            variants.each {
                gradleTasks.add(":app:lint${it.capitalize()}")
                outputFiles.add("lint-results-${it}.html")
            }

            // テスト失敗時にも結果を保存するように try-catch する。
            // Try-catch to save the result even if the test fails.
            Throwable error = null
            try { sh "./gradlew --stacktrace ${gradleTasks.join(' ')}" } catch (e) { error = e }
            try {
                publishHTML([
                        target: [
                                reportName: 'Android Lint Report',
                                reportDir: 'app/build/outputs/',
                                reportFiles: outputFiles.join(','),
                        ],
                        // Lint でエラーが発生した場合はリポートファイルがないことを許容する。
                        // Allow Lint to have no report file if an error occurs.
                        allowMissing: error != null,
                        alwaysLinkToLastBuild: true,
                        keepAll: true,
                ])
            } catch (e) { if (error == null) error = e }
            if (error != null) throw error
        }

        stage 'Local Unit Test'
        withGheStatusSender('Local Unit Test', env.GIT_COMMIT, 'Testing') {
            runTestAndArchiveResult(':app:test', 'app/build/test-results', '*/TEST-*.xml')
        }

        // Emulator にバージョンアップで以下のものが使えなくなったので一旦コメントアウト。
        // 社内では shell スクリプトで AVD の起動や終了をするようにした。
        // Since the following items can no longer be used with the version upgrade to Emulator, comment out once.
        // In-house, the shell script is used to start and stop AVD
        /*
        stage 'Instrumented Test'
        try {
            sh './gradlew :avd:startAvd'
            sh './gradlew connectedAndroidTest'
        } finally {
            sh './gradlew :avd:killAvd'
        }
        */
    }   
}

/** GHE にステータスを通知する。 */
void postGheStatus(Map<String, String> params) {
    // 送信する JSON をファイルに書き出しておく。
    String jsonFileName = 'jenkins_pipeline_input_json.temp'
    String jsonContent = groovy.json.JsonOutput.toJson([
            state: params['state'],
            target_url: env.BUILD_URL,
            description: params['description'],
            context: params['context'],
    ])

    writeFile file: jsonFileName, text: jsonContent
    // 実際に送信する際にはここのコメントアウトを外す。
    /*
    sh 'curl --insecure -H "Authorization: token ' + C.GHE_TOKEN + '" ' +
            '"' + C.GHE_API + '/repos/' + C.GHE_REPO + '/statuses/' + params['commitHash'] + '" ' +
            '-X POST ' +
            '-d @' + jsonFileName
    */
}

/**
 * GHE へのステータス通知を行ってタスクの実行を行う。
   The chair of the GHE chair.
 * タスク実行前に pending 状態を通知し、タスク完了後に、タスクの結果に応じて成功か失敗の状態を通知する。
   Notify the pending status before executing the task, and notify the success or failure status after the task is completed, depending on the result of the task.
 */
void withGheStatusSender(String context, String commitHash, String firstDescription, Closure task) {
    postGheStatus(context: context, commitHash: commitHash, state: 'pending', description: firstDescription)
    try {
        task()
        postGheStatus(context: context, commitHash: commitHash, state: 'success', description: 'Success')
    } catch (e) {
        postGheStatus(context: context, commitHash: commitHash, state: 'failure', description: 'Failure')
        throw e
    }
}

/** Slack に投稿する。 */
/** Post to Slack. */
void postSlack(String text, boolean useNgJenkinsIcon) {
    String slackUrl = C.SLACK_WEBHOOK_URL
    String iconImageUrl = useNgJenkinsIcon ?
            '成功時の Jenkins アイコン' :
            '失敗時の Jenkins アイコン'
    String payload = groovy.json.JsonOutput.toJson([
            text: text,
            icon_url: iconImageUrl,
    ])
    // 実際に送信する際にはここのコメントアウトを外す。//Uncomment here when actually sending.
    //sh "curl -X POST --data-urlencode \'payload=${payload}\' ${slackUrl}"
}

/** タスクを実行し、実行後に成功か失敗かを Slack に投稿する。 */
//Execute the task and post to Slack whether it succeeded or failed after execution.
void withSlackNotifier(Closure task) {
    echo "branch name : ${env.BRANCH_NAME}"
    try {
        task()
        postSlack("Job for branch `${env.BRANCH_NAME}` succeeded! (<${env.BUILD_URL}|Open>)", false)
    } catch (e) {
        postSlack("Job for branch `${env.BRANCH_NAME}` failed! (<${env.BUILD_URL}|Open>)", true)
        throw e
    }
}

void runTestAndArchiveResult(String gradleTestTask, String resultsDir, String resultFilesPattern) {
    // `test` タスクの入力と出力の両方とも更新がなければ `test` タスクがスキップされる (UP-TO-DATE) ので、
    // スキップされないように出力ディレクトリを消しておく。
    // The `test` task will be skipped (UP-TO-DATE) if both the input and output of the` test` task are not updated.
    // Delete the output directory so that it will not be skipped.
    sh "rm -rf ${resultsDir}"

    // テスト失敗時にも結果を保存するように try-catch する//// Try-catch to save the result even if the test fails.
    Throwable error = null
    try { sh "./gradlew --stacktrace ${gradleTestTask}" } catch (e) { error = e }
    try {
        step $class: 'JUnitResultArchiver', testResults: "${resultsDir}/${resultFilesPattern}"
    } catch (e) { if (error == null) error = e }
    if (error != null) throw error
}

我发现建议需要安装脚本安全插件,但我不确定。有什么建议可以解决它吗?

2个回答

如果没有可用的行号,而且在堆栈跟踪之后的 Jenkinsfile 中,制表符的格式似乎非常糟糕,因此很难调试。

但是我怀疑可能是这一行 sudo sh './gradlew assemble' ,因为 sudo 不是有效的 groovy 命令。如果您想以 sudo 身份运行 gradlew,那么它将是 sh 'sudo ./gradlew assemble'

apr_1985
2020-12-21

在尝试在带有 map 参数的方法调用中使用 尾随逗号 时也收到此错误。

简化示例:

sleep time:100, unit:'MILLISECONDS',
    sh "echo error"

— 生成

groovy.lang.MissingPropertyException: No such property: sh for class: groovy.lang.Binding

类似地,

sleep time:100, unit:'MILLISECONDS',
    sleep 1

— 生成

groovy.lang.MissingPropertyException: No such property: sleep for class: groovy.lang.Binding


对我来说,修复方法是删除尾随逗号,即:

sleep time:100, unit:'MILLISECONDS'
    sh 'echo no error'

显然,至少在 GroovySystem.version 2.4.21 中,尾随逗号语法仅在列表 & 中受支持;映射文字( [10, 20, 30,][foo: bar,] )——但不在方法调用中。

ulidtko
2024-02-09