web-dev-qa-db-ja.com

Jenkins Pipeline NotSerializableException:groovy.json.internal.LazyMap

Solved回答の下 S.Richmondのおかげです。変数envServersobjectを無効化することを意味するgroovy.json.internal.LazyMapタイプの保存されたマップを設定解除allする必要がありましたつかいます。

Additional:このエラーを検索する人は、代わりにJenkinsパイプラインステップreadJSONを使用することに興味があるかもしれません-詳細情報を見つける こちら


Jenkins Pipelineを使用して、json文字列としてジョブに渡されるユーザーからの入力を取得しようとしています。その後、パイプラインはこれをslurperを使用して解析し、重要な情報を抽出します。次に、その情報を使用して、1つのジョブを異なるジョブパラメーターと並行して複数回実行します。

以下のコードを追加するまで"## Error when below here is added"スクリプトは問題なく実行されます。そのポイントより下のコードでさえ、単独で実行されます。しかし、結合すると、次のエラーが発生します。

トリガーされたジョブが呼び出され、正常に実行されますが、以下のエラーが発生してメインジョブが失敗することに注意してください。このため、メインジョブは、トリガーされたジョブの復帰を待機しません。私はcouldbuild job:を試行/キャッチしましたが、トリガーされたジョブが終了するまでメインジョブを待機させます。

誰でもここで支援できますか?さらに情報が必要な場合はお知らせください。

乾杯

def slurpJSON() {
return new groovy.json.JsonSlurper().parseText(BUILD_CHOICES);
}

node {
  stage 'Prepare';
  echo 'Loading choices as build properties';
  def object = slurpJSON();

  def serverChoices = [];
  def serverChoicesStr = '';

  for (env in object) {
     envName = env.name;
     envServers = env.servers;

     for (server in envServers) {
        if (server.Select) {
            serverChoicesStr += server.Server;
            serverChoicesStr += ',';
        }
     }
  }
  serverChoicesStr = serverChoicesStr[0..-2];

  println("Server choices: " + serverChoicesStr);

  ## Error when below here is added

  stage 'Jobs'
  build job: 'Dummy Start App', parameters: [[$class: 'StringParameterValue', name: 'SERVER_NAME', value: 'TestServer'], [$class: 'StringParameterValue', name: 'SERVER_DOMAIN', value: 'domain.uk'], [$class: 'StringParameterValue', name: 'APP', value: 'application1']]

}

エラー:

Java.io.NotSerializableException: groovy.json.internal.LazyMap
    at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.Java:860)
    at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.Java:569)
    at org.jboss.marshalling.river.BlockMarshaller.doWriteObject(BlockMarshaller.Java:65)
    at org.jboss.marshalling.river.BlockMarshaller.writeObject(BlockMarshaller.Java:56)
    at org.jboss.marshalling.MarshallerObjectOutputStream.writeObjectOverride(MarshallerObjectOutputStream.Java:50)
    at org.jboss.marshalling.river.RiverObjectOutputStream.writeObjectOverride(RiverObjectOutputStream.Java:179)
    at Java.io.ObjectOutputStream.writeObject(Unknown Source)
    at Java.util.LinkedHashMap.internalWriteEntries(Unknown Source)
    at Java.util.HashMap.writeObject(Unknown Source)
...
...
Caused by: an exception which occurred:
    in field delegate
    in field closures
    in object org.jenkinsci.plugins.workflow.cps.CpsThreadGroup@5288c
53
Sunvic

今日私は自分でこれに遭遇しましたが、いくつかのブルートフォースを通じて、それを解決する方法と潜在的な理由の両方を見つけました。

おそらくその理由から始めるのが最善でしょう:

Jenkinsには、サーバーの再起動によってすべてのジョブを中断、一時停止、および再開できるパラダイムがあります。これを実現するには、パイプラインとそのデータを完全にシリアル化できる必要があります-IEは、すべての状態を保存できる必要があります。同様に、ビルド内のノードとサブジョブ間でグローバル変数の状態をシリアル化できる必要があります。これはあなたと私にとって起こっていることであり、その追加のビルドステップを追加した場合にのみ発生します。

何らかの理由で、JSONObjectはデフォルトでシリアル化できません。私はJava devではないので、悲しいことにこのトピックについてこれ以上語ることはできません。 GroovyとJenkinsにどのように適用できるかはわかりませんが、これを適切に修正する方法についてはたくさんの答えがあります。 この投稿を参照 もう少し情報が必要です。

修正方法:

方法がわかっていれば、JSONObjectを何らかの方法でシリアライズ可能にすることができます。それ以外の場合は、そのタイプのグローバル変数がないことを確認することで解決できます。

object変数の設定を解除するか、メソッドでラップして、スコープがノードグローバルにならないようにしてください。

41
S.Richmond

代わりにJsonSlurperClassicを使用してください。

Groovy 2.3以降(注:Jenkins 2.7.1はGroovy 2.4.7を使用します)JsonSlurperは、LazyMapではなくHashMapを返します。これにより、JsonSlurpernotスレッドセーフおよびnotシリアル化可能。これにより、パイプラインDSLスクリプトの@NonDSL関数以外では使用できなくなります。

ただし、古い behavior をサポートし、パイプラインスクリプト内で安全に使用できるgroovy.json.JsonSlurperClassicにフォールバックできます。

import groovy.json.JsonSlurperClassic 


@NonCPS
def jsonParse(def json) {
    new groovy.json.JsonSlurperClassic().parseText(json)
}

node('master') {
    def config =  jsonParse(readFile("config.json"))

    def db = config["database"]["address"]
    ...
}    

追伸JsonSlurperClassicを呼び出す前に承認する必要があります。

92
luka5z

EDIT:コメントの @ Sunvic で指摘されているように、以下のソリューションはJSONアレイではそのままでは機能しません。

これに対処するには、JsonSlurperを使用し、遅延結果から新しいHashMapを作成しました。 HashMapSerializableです。

これには、new HashMap(Map)JsonSlurperの両方のホワイトリスト登録が必要だと思います。

@NonCPS
def parseJsonText(String jsonText) {
  final slurper = new JsonSlurper()
  return new HashMap<>(slurper.parseText(jsonText))
}

全体として、 Pipeline Utility Stepsプラグイン を使用することをお勧めします。これには readJSON step ワークスペース内のファイルまたはテキストをサポートできます。

11
mkobit

これは、求められた詳細な回答です。

設定解除は私のために働いた:

String res = sh(script: "curl --header 'X-Vault-Token: ${token}' --request POST --data '${payload}' ${url}", returnStdout: true)
def response = new JsonSlurper().parseText(res)
String value1 = response.data.value1
String value2 = response.data.value2

// unset response because it's not serializable and Jenkins throws NotSerializableException.
response = null

解析された応答から値を読み取り、オブジェクトが不要になったら設定を解除します。

4
Nils El-Himoud

配列とマップのデコードを可能にする@mkobitからの回答のも​​う少し一般化された形式は次のとおりです。

import groovy.json.JsonSlurper

@NonCPS
def parseJsonText(String json) {
  def object = new JsonSlurper().parseText(json)
  if(object instanceof groovy.json.internal.LazyMap) {
      return new HashMap<>(object)
  }
  return object
}

注:これはトップレベルのLazyMapオブジェクトのみをHashMapに変換することに注意してください。ネストされたLazyMapオブジェクトは引き続き存在し、Jenkinsで引き続き問題が発生します。

4
TomDotTom

私は答えの1つに賛成したいと思います:ワークスペース内のファイルまたはテキストのいずれかをサポートできるreadJSONステップがあるため、Pipeline Utility Stepsプラグインを使用することをお勧めします: https://jenkins.io/doc/pipeline/steps/pipeline-utility-steps /#readjson-read-json-from-files-in-the-workspace

script{
  def foo_json = sh(returnStdout:true, script: "aws --output json XXX").trim()
  def foo = readJSON text: foo_json
}

これにはホワイトリストや追加のものは必要ありません。

3
Regnoult

パイプラインプラグインの実装方法は、重要なGroovyコードに非常に深刻な影響を及ぼします。このリンクは、起こりうる問題を回避する方法を説明しています: https://github.com/jenkinsci/pipeline-plugin/blob/master/TUTORIAL.md#serializing-local-variables

特定のケースでは、@NonCPSアノテーションをslurpJSONに追加し、JSONオブジェクトではなくmap-of-mapsを返すことを検討します。コードがきれいに見えるだけでなく、特にそのJSONが複雑な場合は、より効率的です。

2
Marcin Płonka

私のNoobの間違い。古いパイプラインプラグインであるjenkins 1.6から誰かのコードを移動しましたか?最新の2.xジェンキンを実行しているサーバーへ。

この理由で失敗しました: "Java.io.NotSerializableException:groovy.lang.IntRange"上記のエラーのために、この投稿を何度も読み続けています。実現:for(1..numSlavesのnum){IntRange-シリアル化できないオブジェクトタイプ。

単純な形式で書き直します:for(num = 1; num <= numSlaves; num ++)

すべては世界に良いです。

Javaやgroovyはあまり使用しません。

みんなありがとう。

1
mpechner

この投稿の他のアイデアは役に立ちましたが、私が探していたすべてではありませんでした-私は自分のニーズに合った部品を抽出し、自分のマジックをいくつか追加しました...

def jsonSlurpLaxWithoutSerializationTroubles(String jsonText)
{
    return new JsonSlurperClassic().parseText(
        new JsonBuilder(
            new JsonSlurper()
                .setType(JsonParserType.LAX)
                .parseText(jsonText)
        )
        .toString()
    )
}

はい、私がコードのgitコミットで述べたように、"Wildly-ineffecient、but tiny coefficient:JSON Slurp solution"(この目的で大丈夫です)。私が解決するために必要な側面:

  1. JSONテキストがネストされたコンテナを定義している場合でも、Java.io.NotSerializableException問題から完全に逃れます
  2. マップコンテナーと配列コンテナーの両方に対応
  3. LAX解析のサポート(私の状況にとって最も重要な部分)
  4. 実装が簡単(@NonCPSを不要にする厄介なネストされたコンストラクターでも)
0
Stevel

Jenkinsパイプラインのドキュメント外 でもっと簡単な方法を見つけました

作業例

import groovy.json.JsonSlurperClassic 


@NonCPS
def jsonParse(def json) {
    new groovy.json.JsonSlurperClassic().parseText(json)
}

@NonCPS
def jobs(list) {
    list
        .grep { it.value == true  }
        .collect { [ name : it.key.toString(),
                      branch : it.value.toString() ] }

}

node {
    def params = jsonParse(env.choice_app)
    def forBuild = jobs(params)
}

JENKINS-26481 -ワークフローの制限により、Groovyクロージャーまたはクロージャーに依存する構文を実際に使用することはできないため、.collectEntriesを使用するGroovy標準を実行することはできません。結果のエントリの値としてステップをリストおよび生成します。また、Forループに標準> Java構文を使用することもできません-つまり、「for(String s:strings)」-代わりに、古い学校のカウンターベースのforループを使用する必要があります。

0
Kirill K