Jib: Support for Grails 4

Created on 8 Jul 2020  路  10Comments  路  Source: GoogleContainerTools/jib

Environment:

  • Jib version: 2.4.0
  • Build tool: Gradle 5.1.1
  • OS: Windows 10

Description of the issue:
Unable to use jib with Grails 4. When attempting to do so, it appears that Grails Views are not assembled into the Docker image, leading to HTTP 500 errors when viewing pages.

Grails has a guide for integrating with Docker using gradle-docker-plugin or manually available here: https://guides.grails.org/grails-as-docker-container/guide/index.html. At that location, you can download a zip file that has an initial Grails project and a Grails project that integrates with gradle-docker-plugin. There is currently not documentation on integrating Grails with jib.

Expected behavior:
Commands "gradle jib", "gradle jibDockerBuild", and "gradle jibBuildTar" assemble Grails Views into the Docker image.

Steps to reproduce:

  1. Download the zip file here: https://guides.grails.org/grails-as-docker-container/guide/index.html#howto and unzip to use the "initial" project.
  2. Apply jib plugin to build.gradle.
  3. Use any of the above three commands such as "gradle jibBuildTar" to build the Docker image. Then load the image and run it in Docker.

Jib config
I've removed the from and to images, but the configuration looks like this:

jib {
  from {
    image = '...'
  }
  to {
    image = '...'
  }
}
kinquestion

All 10 comments

We generally rely on the community to help us integrate with systems like this. Perhaps you could consult the grails team as they were the ones that created the guide for docker.

We may eventually take a look at this, but we currently do not have the bandwidth.

The problem here is that the although Grails uses the spring boot Gradle plugin, it also adds extra tasks itself. The Gradle task that generates the GSON templates is bespoke and doesn't use source sets. This means this plugin does not see the outputs, and so doesn't know it is to include the built resources.

I have got this working with a Grails 4 application, and I can share my config that should help, but you may need to tweak for your needs.

// Commenting out the war plugin is recommended when using with Jib. The default tomcat
// spring boot starter used by Grails doesn't play nicely with the default jetty image used by jib.
// Commenting out the war plugin will cause jib to use the outputs of the jar gradle task, which seems to work
// well.

// apply plugin:"war"

// The "compileGsonViews" task added by Grails does not contribute outputs in the normal way. The below configuration
// ensures that the classes are included in the container image created by jib.
jib {

  // Collect the declared outputs from the compileGsonViews task and create extraDirectory entries in jib
  extraDirectories.paths {
    tasks.compileGsonViews.outputs.files.each { fileEntry ->
      path { 
        from = fileEntry

        // Path in the container where we want to write the gson classes.
        into = '/app/gson-classes' 
      }
    }
  }
  container {

    // Container path we wrote our gson classes to needs adding to the classpath.
    extraClasspath = ['/app/gson-classes']
    exposedPorts = ['8080']
  }
  // ... The rest of your jib config. from, to etc...
}

@sosguthorpe thanks for sharing your config! I guess then the missing piece was to copy compileGsonViews.outputs.files (into /app/gson-classes) and include the destination folder in the runtime classpath (using extraClasspath).

However, one thing I think weird is that you do bootJar.enabled = true, because Jib does not make use bootJar (not using the fat JAR repackaged by Spring Boot, in other words). Even if you set jib.containerizingMode = 'packaged', Jib specially ignores bootJar and uses the JAR by the standard jar task. I think bootJar.enabled = true shouldn't make any difference. If you could share a sample Grails 4 app, I can verify if this is the case. (I tried applying Jib with your config on the "initial" project mentioned in https://github.com/GoogleContainerTools/jib/issues/2569#issue-652707761, but so far no luck.)

@chanseokoh - No need. I can confirm that those changes are unnecessary. By default grails will create an executable war, but the jetty base image is used by jib and the GSON templates are present in the jetty/WEB-INF/classes directory as expected when only the jib specific config is present. I will amend my comment above.

Thanks for the update, @sosguthorpe. That's very interesting.

I liked the part that you didn't apply the war plugin though. I was just saying technically Jib doesn't use the bootJar task even if you are in the non-WAR mode. I recommend the non-WAR approach if that's an option.

We still hope someone to share a simple hello-world Grails app with their working Jib config, to see if there's a better approach. For example, we wondered if it isn't better to tell Gradle that these GSON templates are part of the normal project output than configuring Jib to copy them as extra files.

I understand that Jib doesn't use the bootJar task, the config was to make Grails behave in a consistent way. Disabling the war task would mean a none-executable jar by default. Enabling the bootJar task causes the Grails gradle plugin to change how it's configured. It was totally grails specific.

I created a Hello World App for you to look at. While I did that I noticed 2 things.

  1. When the default tomcat-based spring boot starter is used then the war based image creation fails to start. Tomcat specific logging implementation fails inside the Jetty based container. My project I use Jib with uses the undertow-based starter and that seemed to work OK. I recommend, and we always personally do here, using the jar task based output with Grails and commenting out the war plugin.

    // Commenting out the war plugin is recommended when using with Jib. The default tomcat
    // spring boot starter used by Grails doesn't play nicely with the default jetty image used by jib.
    // Commenting out the war plugin will cause jib to use the outputs of the jar gradle task, which seems to work
    // well.
    // apply plugin:"war"
    
  2. One of the grails plugins' (cache), dependents (gpars) adds a second conflicting version of groovy all to the classpath, which when expanded by jib causes an exception. I added the exclude to the build.gradle file, I personally don't use that plugin and commenting it out will also make the collision go away.

       // Cache plugin depends on GPars, which causes a conflicting version of groovy all on the classpath when used with JIB
       compile ("org.grails.plugins:cache") {
         exclude group: 'org.codehaus.groovy', module: 'groovy-all'
       }
    

The hello world app was created by executing:

$ grails create-app org.world.grails-jib-hello --profile=rest-api

and the only modifications beyond that are the ones documented here to the build gradle file.

Thank you very much for the sample app and the detailed information. This will immensely help the Jib community. To emphasize that we recommend disabling the war plugin, I have updated your earlier and last comments. I have confirmed that the image built by Jib works (after I installed the missing Gradle wrapper binary and ran the compileGsonViews task). We will look into the sample to see if there's an alternative for Grails+Jib. Thanks for sharing your insights!

@chanseokoh - No problem. I did take a look myself, but couldn't quickly figure out how to easily add the results from the compile tasks (there are actually 3 tasks that compile differing types in the base plugin) from the Grails plugin.

I've centralized this config in a plugin we use here so would be interested in the outcome of this in general. If you need any testers give me a shout and I'd be happy to help out where possible. Can't promise to be uber responsive immediately but fit it in where I can.

@sosguthorpe, so the way the jib plugin auto determines what classes to include is based on analysis of the java project structure, specifically the mainSourceSet outputs: https://github.com/GoogleContainerTools/jib/blob/master/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/GradleProjectProperties.java#L220

For some reason the grails gradle plugin considers "views" to be a resources (rather than sources), and this may be some grails convention that I'm not aware of: https://github.com/grails/grails-gradle-plugin/blob/master/src/main/groovy/org/grails/gradle/plugin/GrailsSourceSetConfigurator.groovy#L51

The custom compileGsonViews task also is involved in some non-standard compile workflows, which may be, again, some grails specific workflow I'm not aware of.

Your workaround works well, however, as your point out grails and jib don't work great together out of the box. We cannot provide one-off solutions for this kind of thing in the core jib codbase. It's either up to the Grails plugin devs, who might need to take care to intergrate "normally" into the gradle sourceset structure (not sure if that would be normal in the Grails sense), or you're welcome to write an extension (https://github.com/GoogleContainerTools/jib-extensions) to the jib build that handles grails integration correctly (and easier for other grails users).

Was this page helpful?
0 / 5 - 0 ratings