JenkinsパイプラインNotSerializableException:groovy.json.internal.LazyMap


80

解決済み:S.Richmondからの以下の回答に感謝します。変数を無効にし、使用した後、そのタイプの保存されているすべてのマップの設定を解除する必要がありました。groovy.json.internal.LazyMapenvServersobject

追加:このエラーを検索している人は、readJSON代わりにJenkinsパイプラインステップを使用することに興味があるかもしれません-詳細については、こちらをご覧ください。


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

以下のコードを追加するまで"## Error when below here is added"、スクリプトは正常に実行されます。そのポイントより下のコードでさえ、それ自体で実行されます。しかし、組み合わせると、以下のエラーが発生します。

トリガーされたジョブが呼び出されて正常に実行されますが、以下のエラーが発生してメインジョブが失敗することに注意してください。このため、メインジョブはトリガーされたジョブの戻りを待機しません。私は可能性のtry / catchをまわりbuild 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

自分でこれに遭遇しただけです。もう少し進歩しましたか?
S.Richmond

回答:


71

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

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

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

何らかの理由で、JSONObjectはデフォルトでシリアル化できません。私はJava開発者ではないので、悲しいことにこのトピックについてこれ以上話すことはできません。GroovyとJenkinsにどれだけ適用できるかはわかりませんが、これを適切に修正する方法については、たくさんの答えがあります。詳細については、この投稿参照してください。

修正方法:

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

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


2
おかげで、それは私がこれを解決するために必要な手がかりです。私はすでにあなたの提案を試しましたが、それは私を再び見させ、私が他の変数にマップの一部を保存しているとは考えていませんでした-これらはエラーを引き起こしていました。だから私もそれらの設定を解除する必要がありました。コードへの正しい変更を含めるように私の質問を修正します。乾杯
Sunvic 2016年

1
これは1日に約8回表示されます。このソリューションを実装する方法のより詳細な例を提供していただけませんか?
ジョーダンステファネッリ2016年

1
それはあなたが何をしたかに依存するので、簡単な解決策はありません。ここで提供されている情報と、@ Sunvicが投稿の上部に追加したソリューションは、独自のコードのソリューションに導くのに十分であると言えます。
S.Richmond

1
JsonSlurperClassicを使用した以下の解決策は、私が抱えていたのとまったく同じ問題を修正しましたが、おそらくここで承認された選択肢になるはずです。この答えにはメリットがありますが、この特定の問題に対する正しい解決策ではありません。
クォーツ

@JordanStefanelliソリューションのコードを投稿しました。以下の私の答えを参照しください
Nils El-Himoud 2018

127

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

Groovy 2.3(注:Jenkins2.7.1はGroovy2.4.7を使用なので、の代わりにJsonSlurper戻ります。これにより、スレッドセーフではなく、シリアル化できない新しい実装が作成されます。これにより、パイプラインDSLスクリプトの@NonDSL関数の外部では使用できなくなります。LazyMapHashMapJsonSlurper

ただし、フォールバックし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"]
    ...
}    

ps。JsonSlurperClassicそれが呼び出される前に、あなたはまだ承認する必要があります。


2
承認方法を教えてくださいJsonSlurperClassic
mybecks 2016

7
Jenkins管理者は、Jenkinsの管理»インプロセススクリプト承認に移動する必要があります。
luka5z 2016

残念ながら、私はしか得られませんhudson.remoting.ProxyException: org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed: Script1.groovy: 24: unable to resolve class groovy.json.JsonSlurperClassic
dvtoever 2016年

13
JsonSluperClassic ..この名前は、ソフトウェア開発の現在の状態について多くのことを示しています
Marcos Brigante

1
この詳細な説明をありがとうございました。あなたは私の時間をたくさん節約しました。このソリューションは、私のjenkinsパイプラインの魅力のように機能します。
SathishPrakasam20年

16

編集:コメントで@Sunvicが指摘しているように、以下のソリューションはJSON配列に対してそのままでは機能しません。

私はこれに対処するために、怠惰な結果を使用JsonSlurperして新しいものHashMapを作成しました。HashMapですSerializable

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

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

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


1
私はうまくいきませんでした-エラーが発生し続けましたCould not find matching constructor for: java.util.HashMap(java.util.ArrayList)。ドキュメントは、リストまたはマップを吐き出す必要があることを示唆しています-マップを返すようにどのように構成しますか?
Sunvic 2016

@Sunvic良いキャッチ、私たちが解析しているデータは常にオブジェクトであり、JSON配列ではありません。JSON配列を解析しようとしていますか?
mkobit 2016

はい、それはJSON配列です。
Sunvic 2016

Jenkinsがサンドボックス
環境で

@yiwen管理者のホワイトリストが必要だと言いましたが、それが何を意味するのかについて答えを明確にすることができるでしょうか。
mkobit 2016

8

答えの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
}

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


6

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

未設定は私のために働いた:

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

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


5

@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で引き続き問題が発生します。


2

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

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


2

Jenkinsブログに投稿されたベストプラクティス(パイプラインスケーラビリティのベストプラクティス)によると、この種の作業にはコマンドラインツールまたはスクリプトを使用することを強くお勧めします

落とし穴:特にGroovyのXmlSlurperとJsonSlurperを使用したパイプラインXMLまたはJSON解析は避けてください!コマンドラインツールまたはスクリプトを強くお勧めします。

私。Groovyの実装は複雑であり、その結果、パイプラインの使用がより脆弱になります。

ii。XmlSlurperとJsonSlurperは、パイプラインで高いメモリとCPUのコストを負担する可能性があります

iii。xmllintおよびxmlstartletは、xpathを介したXML抽出を提供するコマンドラインツールです。

iv。jqはJSONに対して同じ機能を提供します

v。これらの抽出ツールは、HTTPAPIから情報をフェッチするためにcurlまたはwgetに結合できます。

したがって、このページで提案されているほとんどのソリューションが、Jenkinsセキュリティスクリプトプラグインのサンドボックスによってデフォルトでブロックされる理由を説明します。

Groovyの言語哲学は、PythonやJavaよりもBashに近いものです。また、ネイティブのGroovyで複雑で重い作業を行うのは自然ではないことを意味します。

それを踏まえて、私は個人的に以下を使用することにしました:

sh('jq <filters_and_options> file.json')

詳細については、jqManualおよびSelectobjects with jq stackoverflowpostを参照してください。

Groovyはデフォルトのホワイトリストにない多くのジェネリックメソッドを提供するため、これは少し直感に反します。

とにかくほとんどの作業でGroovy言語を使用し、サンドボックスを有効にしてクリーンにすることにした場合(これは自然ではないため簡単ではありません)、セキュリティスクリプトプラグインのバージョンのホワイトリストをチェックして、可能性を確認することをお勧めします:スクリプトセキュリティプラグインのホワイトリスト


2

次の関数を使用して、LazyMapを通常のLinkedHashMapに変換できます(元のデータの順序を保持します)。

LinkedHashMap nonLazyMap (Map lazyMap) {
    LinkedHashMap res = new LinkedHashMap()
    lazyMap.each { key, value ->
        if (value instanceof Map) {
            res.put (key, nonLazyMap(value))
        } else if (value instanceof List) {
            res.put (key, value.stream().map { it instanceof Map ? nonLazyMap(it) : it }.collect(Collectors.toList()))
        } else {
            res.put (key, value)
        }
    }
    return res
}

... 

LazyMap lazyMap = new JsonSlurper().parseText (jsonText)
Map serializableMap = nonLazyMap(lazyMap);

または、以前のコメントで気づいたように、readJSONステップを使用することをお勧めします。

Map serializableMap = readJSON text: jsonText

1

この投稿の他のアイデアは役に立ちましたが、私が探していたものがすべてではありませんでした。そこで、必要に応じてパーツを抽出し、独自のマジックをいくつか追加しました...

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

はい、コードの私自身のgit commitで述べたように、「非常に非効率的ですが、小さな係数:JSONスラップソリューション」(この目的には問題ありません)。私が解決する必要のある側面:

  1. 完全に離れて java.io.NotSerializableExceptionJSONテキストがネストされたコンテナーを定義している場合でも問題を回避します
  2. マップコンテナと配列コンテナの両方で機能します
  3. LAX解析をサポートする(私の状況で最も重要な部分)
  4. 実装が簡単です(厄介なネストされたコンストラクターが不要な場合でも@NonCPS

1

私の側のNoobの間違い。古いパイプラインプラグイン、jenkins 1.6から誰かのコードを移動しましたか?最新の2.xjenkinsを実行しているサーバーに。

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

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

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

私はjavaやgroovyをあまり使用しません。

みんなありがとう。


0

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ループを使用する必要があります。


1
代わりにJenkinsパイプラインステップreadJSONを使用することをお勧めします-詳細については、こちらをご覧ください。
Sunvic 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.