opendevstack / ods-project-quickstarters

CAUTION! This repo is DEPRECATED. For ODS 2+, please see ods-quickstarters repository.
Apache License 2.0
10 stars 19 forks source link

invalid nexus deploy script on be-spring-boot quickstarter #330

Closed stefanlack closed 5 years ago

stefanlack commented 5 years ago

Task bootJar on generated gradle project is modified and writes jar archive to _docker/app.jar':

bootJar {
    archiveName    "app.jar"
    destinationDir  file("$buildDir/../docker")
}

Task uploadArchives is invalid, because it expects the jar file in build/libs/be-spring-boot..jar.

stefanlack commented 5 years ago

Suggested fix. Modify bootJar Task, so that jar file is generated in default gradle build/libs directory, copy jar file from this location to destianation directory docker and rename it to app.jar:

bootJar {
    doLast {
        println "Copy jar file to docker directory...file build/libs/$bootJar.archiveName"
        copy {
            from "build/libs/$bootJar.archiveName"
            into "$buildDir/../docker"
            rename(bootJar.archiveName,"app.jar")
        }
    }
}
uploadArchives {
    repositories{
        mavenDeployer {
            repository(url: "${nexus_url}/repository/${nexus_folder}/") {
                 authentication(userName: "${nexus_user}", password: "${nexus_pw}")
            }
            pom.artifactId = 'be-spring-boot'
            pom.groupId = 'org.opendevstack.tstsla03'
            pom.version="${System.getenv("TAGVERSION")}" // we will get a TAGVERSION from environment
        }
    }
}

Modification in Jenkinsfile:

def stageBuild(def context) {
  def javaOpts = "-Xmx512m"
  def gradleTestOpts = "-Xmx128m"
  def springBootEnv = context.environment
  if (springBootEnv.contains('-dev')) {
    springBootEnv = 'dev'
  }
  stage('Build') {
    withEnv(["TAGVERSION=${context.tagversion}", "NEXUS_HOST=${context.nexusHost}", "NEXUS_USERNAME=${context.nexusUsername}", "NEXUS_PASSWORD=${context.nexusPassword}", "JAVA_OPTS=${javaOpts}","GRADLE_TEST_OPTS=${gradleTestOpts}","ENVIRONMENT=${springBootEnv}"]) {
      def status = sh(script: "./gradlew clean build --stacktrace --no-daemon", returnStatus: true)
      junit 'build/test-results/test/*.xml'
      if (status != 0) {
        error "Build failed!"
      } else {
          def deployStatus = sh(script: "./gradlew uploadArchives --stacktrace --no-daemon", returnStatus: true)
          if (deployStatus != 0) {
            error "Deployment to nexus failed!"
          }
      }
    }
  }
}
stitakis commented 5 years ago

Hi @stefanlack I think the build stage can be simplifyed by just adding uploadArchives in first call to gradlew after build. It would look like ./gradlew clean build uploadArchives. However and apart from that, it looks to me not necessary to upload the jar file to nexus for these reasons:

stefanlack commented 5 years ago

@stitakis good points. Eighter fix broken upload artifacts or remove upload to nexus should be done.

So my suggestion:

Any thoughts regarding my suggestion?

rattermeyer commented 5 years ago

Yes, but your assumption is only right, if docker image is the exhange format. That is not always the case. We develop on ODS, but our customers still require WAR and JAR files to be delivered.

On the other hand it has an operational problem. If you only have the jar file in the docker image and the underlying image (e.g. openjdk) has a security issue, you cannot simply "rebuild" the image and redeploy it on production, but you have to go through the complete process for your app.

michaelsauter commented 5 years ago

@rattermeyer You are right that one needs to rebuild if the underlying image changes. Though certainly not ideal, I think it is worth the trade-off. In the beginning, we had upload to Nexus, and download from there into the Docker image and it was a complicated, brittle setup.

In the end, a quickstarter can never cover all use cases. Right now, a quickstarter means "deployment on OpenShift", and if you need sth. else you can modify the generated files.

That said, I think it would be nice to show how to upload to Nexus instead of deploying the Docker image. So maybe a disabled/commented stage in the Jenkinsfile that shows how to upload could be a compromise.

rattermeyer commented 5 years ago

Agree on the optional part. But what was the brittle part in the setup? Have done it for year (upload to nexus, download as part of the image build)

stitakis commented 5 years ago

@all for uploading maven artifacts the current uploadArtifacts does not provide the proper functionality. It just uploads a fat jar without pom. I have a working example that I could share. To fix that we could opt for A. a quickstarter for java libs, B. code to uncomment in gradle build file and jenkinsfile. C. document what changes are required. Your thoughs?

michaelsauter commented 5 years ago

@rattermeyer You need matching paths in both upload / download, and you need to have Nexus credentials during the container build. There is really no need for that if you see Jenkins as the thing that builds artefacts, and then just wrap what is build in the container image. Plus, it feels a bit awkward to have a JAR built, and instead of putting it into the image, you put it into Nexus only to pull it out into the image.

What is the reason of having the JAR in Nexus? Only to not having to rebuild in case there is a security fix in the underlying image? Other than that I don't see the point really as the image is what you are shipping ...

rattermeyer commented 5 years ago

thats fine. lets leave it per default as a copy to docker image and optionally having the option to upload to nexus. As fas as I understand Stefans proposal thats exactly what he does.

stefanlack commented 5 years ago

@rattermeyer : yes, that was my opinion and that is implemented by my suggestion.

stefanlack commented 5 years ago

@stitakis : if you may add a comment with a hint for better uploadArtifacts task implementation: your welcome. Thanks in advance.

stitakis commented 5 years ago

@stefanlack there are some changes to add to build.gradle and Jenkinsfile: build.gradle:

  1. add maven-publish
    plugins {
    ...
    id 'maven-publish'
    }
  2. Add task jar:
    jar {
    enabled = true
    }
  3. replace ùploadArchives` for:

    publishing {
    repositories {
        maven {
            def releasesRepoUrl = "${nexus_url}/repository/candidates"
            def snapshotsRepoUrl ="${nexus_url}/repository/snapshots"
            url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
    
            credentials {
                username = "${nexus_user}"
                password = "${nexus_pw}"
            }
    
        }
    }
    publications {
        maven(MavenPublication) {
            artifactId = 'be-java-lib'
            groupId = 'com.bix-digital.sebio'
            version = "${version}"
    
            from components.java
        }
    }
    }

    Please note the different repo urls and the url resolution based in version name.

Jenkinsfile:

  1. add to build stage to the ./gradlew command the parameter -P branchName=${context.gitBranch}
  2. add stage publish to nexus
    def stagePublishLibToNexus(def context) {
    def javaOpts = "-Xmx512m"
    stage('Publish Library to Nexus') {
    withEnv(["TAGVERSION=${context.tagversion}", "NEXUS_HOST=${context.nexusHost}", "NEXUS_USERNAME=${context.nexusUsername}", "NEXUS_PASSWORD=${context.nexusPassword}", "JAVA_OPTS=${javaOpts}"]) {
      sh(script: "ls -lart ./build/libs")
      def status = sh(script: "./gradlew publish -P branchName=${context.gitBranch} --stacktrace --no-daemon", returnStatus: true)
      junit 'build/test-results/test/*.xml'
      if (status != 0) {
        error "Build failed!"
      }
    }
    }
    }

Note: this implementation will change the version of the artifact by removing the suffx SNAPSHOT when the branch is being merged to master. This allow the development team to do library releases