AIを使ってしたい100のこと

生成AIの可能性と、それを使って自分自身を表現する楽しさを伝えることを目指しています。

複数のECRからOpenShiftへのコンテナイメージの一括コピーを実現するJenkinsパイプラインの作成

はじめに

今回はちょっと大規模に見えるかもしれません。私はまず、ECRからOpenShiftイメージレジストリにコンテナイメージをコピーするJenkinsパイプライン「ecr-image-copyer.jenkinsfile」を作成しました。 今回はこの、Jenkinsパイプライン「ecr-image-copyer.jenkinsfile」を呼び出して複数回実行する、別のパイプラインを作成します。

呼び出される側のJenkinsパイプライン「ecr-image-copyer.jenkinsfile」は存在するが、呼び出す側のJenkinsパイプラインは、何もない状態です。 そのような、何もない状態の物を生成AIで作成してみることにします。

JenkinsパイプラインでECRからOpenShiftへのコンテナイメージ一括コピー

プロンプト

wrapperを作る

私は、ECRからOpenShiftイメージレジストリにコンテナイメージをコピーするJenkinsパイプライン「ecr-image-copyer.jenkinsfile」を作成しました。

このJenkinsパイプライン「ecr-image-copyer.jenkinsfile」は、1つの「ECR_REPOSITORY」内のコンテナイメージを、「OPENSHIFT_PROJECT」にコピーします。

これから、Jenkinsパイプライン「ecr-image-copyer.jenkinsfile」を複数回実行し、複数の「ECR_REPOSITORY」内のコンテナイメージを、1つの「OPENSHIFT_PROJECT」にコピーするための、新たなJenkinsパイプライン「multi-ecr-image-copyer.jenkinsfile」を作成します。

Jenkinsパイプライン「multi-ecr-image-copyer.jenkinsfile」を作成してください。

「multi-ecr-image-copyer.jenkinsfile」が受け取るJenkinsパイプラインパラメーター

パラメーター1:「ECR_REPOSITORY」

値はtext(以下のような改行付きテキスト) example-repo1 example-repo2

パラメーター2:「OPENSHIFT_PROJECT」

値はstring example-project

「multi-ecr-image-copyer.jenkinsfile」がecr-image-copyerに与えるパラメーター

以下のパラメーター値を、wrapperに与えられた値でオーバーライドする。

ECR_REPOSITORY
OPENSHIFT_PROJECT

以下のパラメーターのdefaultValueを使う。

    string(name: 'AWS_ACCOUNT_ID', defaultValue: '123456789012', description: 'AWS Account ID')
    string(name: 'AWS_REGION', defaultValue: 'us-west-2', description: 'AWS region')
    string(name: 'AWS_ACCESS_KEY_ID', defaultValue: 'ABCD1234EFGH5678IJKL', description: 'AWS Access Key ID')
    string(name: 'AWS_SECRET_ACCESS_KEY', defaultValue: 'abcd1234efgh5678ijklmnopqrstuvwx', description: 'AWS Secret Access Key')

    string(name: 'OPENSHIFT_REGISTRY', defaultValue: 'image-registry.openshift.example.com:5000', description: 'OpenShift registry')
    string(name: 'OPENSHIFT_REGISTRY_CREDENTIALSID', defaultValue: 'example-credentials', description: 'OpenShift registry credentials ID')

    string(name: 'HTTP_PROXY', defaultValue: 'http://proxy.example.com:8080', description: 'HTTP proxy')
    string(name: 'HTTPS_PROXY', defaultValue: 'http://proxy.example.com:8080', description: 'HTTPS proxy')
    string(name: 'NO_PROXY', defaultValue: 'example-registry.openshift.svc', description: 'No proxy')

ecr-image-copyer.jenkinsfile

pipeline {
  agent {
    kubernetes {
      cloud 'openshift'
      yaml '''
        apiVersion: v1
        kind: Pod
        spec:
          serviceAccountName: jenkins
          affinity:
            nodeAffinity:
              requiredDuringSchedulingIgnoredDuringExecution:
                nodeSelectorTerms:
                - matchExpressions:
                  - key: node-role.kubernetes.io/infra
                    operator: Exists
          containers:
            - name: jnlp
              image: image-registry.openshift.example.com:5000/example-cicd/custom-jenkins-agent
              args: ['\$(JENKINS_SECRET)', '\$(JENKINS_NAME)']
              tty: false
              resources:
                requests:
                  cpu: 500m
                  memory: 1024Mi
                limits:
                  cpu: 500m
                  memory: 1024Mi
      '''
    }
  }
  environment {
    AWS_ACCOUNT_ID = "${params.AWS_ACCOUNT_ID}"
    AWS_REGION = "${params.AWS_REGION}"
    ECR_REPOSITORY = "${params.ECR_REPOSITORY}"
    AWS_ACCESS_KEY_ID = "${params.AWS_ACCESS_KEY_ID}"
    AWS_SECRET_ACCESS_KEY = "${params.AWS_SECRET_ACCESS_KEY}"
    OPENSHIFT_REGISTRY = "${params.OPENSHIFT_REGISTRY}"
    OPENSHIFT_PROJECT = "${params.OPENSHIFT_PROJECT}"
    OPENSHIFT_REGISTRY_CREDENTIALSID = "${params.OPENSHIFT_REGISTRY_CREDENTIALSID}"
    HTTP_PROXY = "${params.HTTP_PROXY}"
    HTTPS_PROXY = "${params.HTTPS_PROXY}"
    NO_PROXY = "${params.NO_PROXY}"
  }

  parameters {
    string(name: 'AWS_ACCOUNT_ID', defaultValue: '123456789012', description: 'AWS Account ID')
    string(name: 'AWS_REGION', defaultValue: 'us-west-2', description: 'AWS region')
    string(name: 'ECR_REPOSITORY', defaultValue: 'example-ecr-repo', description: 'ECR repository name')
    string(name: 'AWS_ACCESS_KEY_ID', defaultValue: 'ABCD1234EFGH5678IJKL', description: 'AWS Access Key ID')
    string(name: 'AWS_SECRET_ACCESS_KEY', defaultValue: 'abcd1234efgh5678ijklmnopqrstuvwx', description: 'AWS Secret Access Key')
    string(name: 'OPENSHIFT_REGISTRY', defaultValue: 'image-registry.openshift.example.com:5000', description: 'OpenShift registry')
    string(name: 'OPENSHIFT_PROJECT', defaultValue: 'example-project', description: 'OpenShift project')
    string(name: 'OPENSHIFT_REGISTRY_CREDENTIALSID', defaultValue: 'example-credentials', description: 'OpenShift registry credentials ID')
    string(name: 'HTTP_PROXY', defaultValue: 'http://proxy.example.com:8080', description: 'HTTP proxy')
    string(name: 'HTTPS_PROXY', defaultValue: 'http://proxy.example.com:8080', description: 'HTTPS proxy')
    string(name: 'NO_PROXY', defaultValue: 'example-registry.openshift.svc', description: 'No proxy')
  }

  stages {
    stage('Get ECR Images') {
      steps {
        script {
          def ecrImages = sh(script: '''
            aws ecr describe-images --repository-name ${ECR_REPOSITORY} --region ${AWS_REGION} \
            --query 'imageDetails[?imageScanStatus.status==`COMPLETE` && imageTags!=null]' \
            --output json
          ''', returnStdout: true).trim()
          env.ECR_IMAGES = ecrImages
        }
      }
    }

    stage('Check for Critical Findings') {
      steps {
        script {
          def criticalImages = sh(script: """
            echo '${ECR_IMAGES}' | jq '[.[] | select(.imageScanFindingsSummary.findingSeverityCounts.CRITICAL > 0)]'
          """, returnStdout: true).trim()

          writeFile file: 'critical_images.json', text: criticalImages
          def criticalImagesJSON = readJSON file: 'critical_images.json'

          if (criticalImagesJSON != '[]') {
            criticalImagesJSON.each { image ->
              def tag = image.imageTags[0]
              def SeverityCountsCRITICAL = image.imageScanFindingsSummary.findingSeverityCounts.CRITICAL
              def messageDetails = "${ECR_REPOSITORY}:${tag} CRITICAL: ${SeverityCountsCRITICAL}"

              echo "Warning: There are images with critical findings. ${messageDetails}"
            }
          }
        }
      }
    }

    stage('Filter Safe Images') {
      steps {
        script {
          def safeImages = sh(script: """
            echo '${ECR_IMAGES}' | jq '[.[] | select(.imageScanFindingsSummary.findingSeverityCounts.CRITICAL == null)]'
          """, returnStdout: true).trim()
          // env.SAFE_IMAGES = safeImages
          writeFile file: 'safe_images.json', text: safeImages
        }
      }
    }

    stage('Copy Images to OpenShift') {
      steps {
        script {
          def images = readJSON file: 'safe_images.json'

          images.each { image ->
            def tag = image.imageTags[0]
            def ecrRepository = "${ECR_REPOSITORY}"
            def imageName = ecrRepository.replaceAll('/', '-')
            def sourceImage = "${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPOSITORY}:${tag}"
            def targetImage = "${OPENSHIFT_REGISTRY}/${OPENSHIFT_PROJECT}/${imageName}:${tag}"

            withCredentials([file(credentialsId: "${OPENSHIFT_REGISTRY_CREDENTIALSID}", variable: 'DEST_AUTHFILE')]) {
              sh(script: """
                skopeo copy --debug \
                --src-creds=AWS:\$(aws ecr get-login-password --region="${AWS_REGION}") \
                --dest-authfile=${DEST_AUTHFILE} \
                --dest-tls-verify=false \
                docker://${sourceImage} \
                docker://${targetImage}
              """)
            }
          }
        }
      }
    }
  }

  post {
    always {
      cleanWs()
    }
  }
}

回答

以下は、指定された要件に基づいて作成したJenkinsパイプライン「multi-ecr-image-copyer.jenkinsfile」です。このパイプラインは、「ecr-image-copyer.jenkinsfile」を呼び出して、複数の「ECR_REPOSITORY」からイメージをコピーします。

pipeline {
    agent any

    parameters {
        text(name: 'ECR_REPOSITORY', defaultValue: '''example-repo1
example-repo2''', description: 'ECR repositories (one per line)')
        string(name: 'OPENSHIFT_PROJECT', defaultValue: 'example-project', description: 'OpenShift project')
    }

    stages {
        stage('Trigger ECR Image Copy Jobs') {
            steps {
                script {
                    def repositories = params.ECR_REPOSITORY.split('\n')
                    def openshiftProject = params.OPENSHIFT_PROJECT

                    repositories.each { repo ->
                        build job: 'ecr-image-copyer', parameters: [
                            string(name: 'ECR_REPOSITORY', value: repo),
                            string(name: 'OPENSHIFT_PROJECT', value: openshiftProject),
                            string(name: 'AWS_ACCOUNT_ID', value: '123456789012'),
                            string(name: 'AWS_REGION', value: 'us-west-2'),
                            string(name: 'AWS_ACCESS_KEY_ID', value: 'ABCD1234EFGH5678IJKL'),
                            string(name: 'AWS_SECRET_ACCESS_KEY', value: 'abcd1234efgh5678ijklmnopqrstuvwx'),
                            string(name: 'OPENSHIFT_REGISTRY', value: 'image-registry.openshift.example.com:5000'),
                            string(name: 'OPENSHIFT_REGISTRY_CREDENTIALSID', value: 'example-credentials'),
                            string(name: 'HTTP_PROXY', value: 'http://proxy.example.com:8080'),
                            string(name: 'HTTPS_PROXY', value: 'http://proxy.example.com:8080'),
                            string(name: 'NO_PROXY', value: 'example-registry.openshift.svc')
                        ]
                    }
                }
            }
        }
    }

    post {
        always {
            cleanWs()
        }
    }
}

説明

  • パラメータ:

    • ECR_REPOSITORY: 改行で区切られたECRリポジトリ名のリスト。
    • OPENSHIFT_PROJECT: OpenShiftプロジェクト名。
  • ステージ:

    • Trigger ECR Image Copy Jobs: 各ECRリポジトリに対してecr-image-copyerジョブをトリガーします。このステージでは、各リポジトリ名と共に必要なパラメータをecr-image-copyerジョブに渡します。
  • ポスト処理:

    • 常に作業スペースをクリーンアップします。

やってみた感想

  • 生成された呼び出し側のJenkinsパイプライン「multi-ecr-image-copyer.jenkinsfile」は、そのまま動作しました。
  • 生成されたパイプラインの「説明」まであり、懇切丁寧ですね。
  • 「別のパイプラインを呼び出す方法」を自分で調べることなく、完成品を手に入れることができました。
  • 「一部のパラメーターを実行時に指定できるようにして、それ以外のパラメーターには初期値を与える。」という指示もきちんと反映されました。
  • 呼び出される側のプログラムコードをまず作り、呼び出す側を生成AIで作るというアプローチは、有効のようです。
    • とりあえず「単数の物を処理するプログラム」を作成すれば、「それを複数回実行する側のプログラム」を生成AIで作成できるわけです。
  • 私も最近は、ずいぶんと長いプロンプトを書くようになってきました。