• 1.30

      Configuration-as-Code is the (proposed) mechanism to configure jenkins master automatically from plain text recipes. It relies on component internal model auto-discovery, and as such assume plugins do follow best practices for UI data-binding and internal design.

       

      Using the export feature to generate a configuration yaml-file

      it's partially supported, but significant configuration attributes don't follow expected design.
      as a resume

      • manual JSON parsing vs data-binding
      • no setters for databound configuration elements
      • optional block vs optional property

      Part of the experienced Exception:

      "FAILED TO EXPORT hudson.plugins.gradle.GradleInstallation$DescriptorImpl\
          \ : \nio.jenkins.plugins.casc.ConfiguratorException: Can't read attribute 'installations'\
          \ from hudson.plugins.gradle.GradleInstallation$DescriptorImpl@2dfa0e31\n\tat\
          \ io.jenkins.plugins.casc.Attribute._getValue(Attribute.java:252)\n\tat io.jenkins.plugins.casc.Attribute.getValue(Attribute.java:166)\n\
          \tat io.jenkins.plugins.casc.Attribute.equals(Attribute.java:199)\n\tat io.jenkins.plugins.casc.BaseConfigurator.compare(BaseConfigurator.java:367)\n\
          \tat io.jenkins.plugins.casc.impl.configurators.DescriptorConfigurator.describe(DescriptorConfigurator.java:65)\n\
          \tat io.jenkins.plugins.casc.impl.configurators.GlobalConfigurationCategoryConfigurator.describe(GlobalConfigurationCategoryConfigurator.java:94)\n\
          \tat io.jenkins.plugins.casc.impl.configurators.GlobalConfigurationCategoryConfigurator.lambda$describe$6(GlobalConfigurationCategoryConfigurator.java:87)\n\
          \tat java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)\n\
          \tat java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)\n\
          \tat java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)\n\
          \tat java.util.Iterator.forEachRemaining(Iterator.java:116)\n\tat java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)\n\
          \tat java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)\n\t\
          at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)\n\
          \tat java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)\n\
          \tat java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)\n\
          \tat java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)\n\t\
          at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)\n\t\
          at io.jenkins.plugins.casc.impl.configurators.GlobalConfigurationCategoryConfigurator.describe(GlobalConfigurationCategoryConfigurator.java:87)\n\
          \tat io.jenkins.plugins.casc.impl.configurators.GlobalConfigurationCategoryConfigurator.describe(GlobalConfigurationCategoryConfigurator.java:30)\n\
          \tat io.jenkins.plugins.casc.ConfigurationAsCode.export(ConfigurationAsCode.java:406)\n\
          \tat io.jenkins.plugins.casc.ConfigurationAsCode.doExport(ConfigurationAsCode.java:396)\n\
          \tat java.lang.invoke.MethodHandle.invokeWithArguments(MethodHandle.java:627)\n\
          \tat org.kohsuke.stapler.Function$MethodFunction.invoke(Function.java:343)\n\t\
          at org.kohsuke.stapler.interceptor.RequirePOST$Processor.invoke(RequirePOST.java:77)\n\
          \tat org.kohsuke.stapler.PreInvokeInterceptedFunction.invoke(PreInvokeInterceptedFunction.java:26)\n\
          \tat org.kohsuke.stapler.Function.bindAndInvoke(Function.java:184)\n\tat org.kohsuke.stapler.Function.bindAndInvokeAndServeResponse(Function.java:117)\n\
          \tat org.kohsuke.stapler.MetaClass$1.doDispatch(MetaClass.java:129)\n\tat org.kohsuke.stapler.NameBasedDispatcher.dispatch(NameBasedDispatcher.java:58)\n\
          \tat org.kohsuke.stapler.Stapler.tryInvoke(Stapler.java:734)\n\tat org.kohsuke.stapler.Stapler.invoke(Stapler.java:864)\n\
          \tat org.kohsuke.stapler.MetaClass$10.dispatch(MetaClass.java:374)\n\tat org.kohsuke.stapler.Stapler.tryInvoke(Stapler.java:734)\n\
          \tat org.kohsuke.stapler.Stapler.invoke(Stapler.java:864)\n\tat org.kohsuke.stapler.Stapler.invoke(Stapler.java:668)\n\
          \tat org.kohsuke.stapler.Stapler.service(Stapler.java:238)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:790)\n\
          \tat org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:865)\n\t\
          at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1655)\n\
          \tat hudson.util.PluginServletFilter$1.doFilter(PluginServletFilter.java:154)\n\
          \tat org.jenkinsci.plugins.ssegateway.Endpoint$SSEListenChannelFilter.doFilter(Endpoint.java:225)\n\
          \tat hudson.util.PluginServletFilter$1.doFilter(PluginServletFilter.java:151)\n\
          \tat io.jenkins.blueocean.ResourceCacheControl.doFilter(ResourceCacheControl.java:134)\n\
          \tat hudson.util.PluginServletFilter$1.doFilter(PluginServletFilter.java:151)\n\
          \tat io.jenkins.blueocean.auth.jwt.impl.JwtAuthenticationFilter.doFilter(JwtAuthenticationFilter.java:61)\n\
          \tat hudson.util.PluginServletFilter$1.doFilter(PluginServletFilter.java:151)\n\
          \tat com.smartcodeltd.jenkinsci.plugin.assetbundler.filters.LessCSS.doFilter(LessCSS.java:47)\n\
          \tat hudson.util.PluginServletFilter$1.doFilter(PluginServletFilter.java:151)\n\
          \tat hudson.util.PluginServletFilter.doFilter(PluginServletFilter.java:157)\n\t\
          at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1642)\n\
          \tat hudson.security.csrf.CrumbFilter.doFilter(CrumbFilter.java:99)\n\tat org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1642)\n\
          \tat hudson.security.ChainedServletFilter$1.doFilter(ChainedServletFilter.java:84)\n\
          \tat hudson.security.UnwrapSecurityExceptionFilter.doFilter(UnwrapSecurityExceptionFilter.java:51)\n\
          \tat hudson.security.ChainedServletFilter$1.doFilter(ChainedServletFilter.java:87)\n\
          \tat jenkins.security.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:117)\n\
          \tat hudson.security.ChainedServletFilter$1.doFilter(ChainedServletFilter.java:87)\n\
          \tat org.acegisecurity.providers.anonymous.AnonymousProcessingFilter.doFilter(AnonymousProcessingFilter.java:125)\n\
          \tat hudson.security.ChainedServletFilter$1.doFilter(ChainedServletFilter.java:87)\n\
          \tat org.acegisecurity.ui.rememberme.RememberMeProcessingFilter.doFilter(RememberMeProcessingFilter.java:142)\n\
          \tat hudson.security.ChainedServletFilter$1.doFilter(ChainedServletFilter.java:87)\n\
          \tat org.acegisecurity.ui.AbstractProcessingFilter.doFilter(AbstractProcessingFilter.java:271)\n\
          \tat hudson.security.ChainedServletFilter$1.doFilter(ChainedServletFilter.java:87)\n\
          \tat jenkins.security.BasicHeaderProcessor.doFilter(BasicHeaderProcessor.java:93)\n\
          \tat hudson.security.ChainedServletFilter$1.doFilter(ChainedServletFilter.java:87)\n\
          \tat org.acegisecurity.context.HttpSessionContextIntegrationFilter.doFilter(HttpSessionContextIntegrationFilter.java:249)\n\
          \tat hudson.security.HttpSessionContextIntegrationFilter2.doFilter(HttpSessionContextIntegrationFilter2.java:67)\n\
          \tat hudson.security.ChainedServletFilter$1.doFilter(ChainedServletFilter.java:87)\n\
          \tat hudson.security.ChainedServletFilter.doFilter(ChainedServletFilter.java:90)\n\
          \tat hudson.security.HudsonFilter.doFilter(HudsonFilter.java:171)\n\tat org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1642)\n\
          \tat org.kohsuke.stapler.compression.CompressionFilter.doFilter(CompressionFilter.java:49)\n\
          \tat org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1642)\n\
          \tat hudson.util.CharacterEncodingFilter.doFilter(CharacterEncodingFilter.java:82)\n\
          \tat org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1642)\n\
          \tat org.kohsuke.stapler.DiagnosticThreadNameFilter.doFilter(DiagnosticThreadNameFilter.java:30)\n\
          \tat org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1642)\n\
          \tat org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:533)\n\
          \tat org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:146)\n\
          \tat org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:524)\n\
          \tat org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)\n\
          \tat org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:257)\n\
          \tat org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1595)\n\
          \tat org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:255)\n\
          \tat org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1317)\n\
          \tat org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:203)\n\
          \tat org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:473)\n\
          \tat org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1564)\n\
          \tat org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:201)\n\
          \tat org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1219)\n\
          \tat org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:144)\n\
          \tat org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)\n\
          \tat org.eclipse.jetty.server.Server.handle(Server.java:531)\n\tat org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:352)\n\
          \tat org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:260)\n\
          \tat org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:281)\n\
          \tat org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:102)\n\tat org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:118)\n\
          \tat org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:333)\n\
          \tat org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:310)\n\
          \tat org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:168)\n\
          \tat org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:126)\n\
          \tat org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:366)\n\
          \tat winstone.BoundedExecutorService.lambda$scheduleNext$0(BoundedExecutorService.java:80)\n\
          \tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)\n\
          \tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)\n\
          \tat java.lang.Thread.run(Thread.java:748)\nCaused by: java.lang.reflect.InvocationTargetException\n\
          \tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n\
          \tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\
          \tat java.lang.reflect.Method.invoke(Method.java:498)\n\tat io.jenkins.plugins.casc.Attribute._getValue(Attribute.java:234)\n\
          \t... 107 more\nCaused by: java.lang.NullPointerException\n\tat hudson.plugins.gradle.GradleInstallation$DescriptorImpl.getInstallations(GradleInstallation.java:120)\n\
          \tat hudson.plugins.gradle.GradleInstallation$DescriptorImpl.getInstallations(GradleInstallation.java:97)\n\
          \t... 112 more\n"
      

       

          [JENKINS-53575] Configuration-as-Code compatibility

          The issue is due to JCasC export feature design : as we only want to export things that differ from default values, we create a fresh new `GradleInstallation$DescriptorImpl` instance and compare properties.
          But gradle plugin did manage the transition from `Gradle.DescriptorImpl` to `ToolDescriptor` relying on an `@Inject` delegate, which I think is a nice, concise and clean way to handle this. When JCasC do create a new instance it doesn't inject dependencies and the delegate call results in a NPE.

          I'm not sure the best way to handle this. I like the @Inject-based delegate approach, compared to other implementations which do a systematic lookup to legacy Descriptor. On the other hand the root issue maybe is that those approach don't migrate data, they just pretend they implement the new API but tried to avoid having to manage data migration. Maybe a correct way to handle this is for `Gradle.DescriptorImpl` (and other) to populate `GradleInstallation$DescriptorImpl#installations` when some legacy data get loaded, and fully follow the intended `ToolDescriptor` design to host `installations` property.

          Nicolas De Loof added a comment - The issue is due to JCasC export feature design : as we only want to export things that differ from default values, we create a fresh new `GradleInstallation$DescriptorImpl` instance and compare properties. But gradle plugin did manage the transition from `Gradle.DescriptorImpl` to `ToolDescriptor` relying on an `@Inject` delegate, which I think is a nice, concise and clean way to handle this. When JCasC do create a new instance it doesn't inject dependencies and the delegate call results in a NPE. I'm not sure the best way to handle this. I like the @Inject-based delegate approach, compared to other implementations which do a systematic lookup to legacy Descriptor. On the other hand the root issue maybe is that those approach don't migrate data, they just pretend they implement the new API but tried to avoid having to manage data migration. Maybe a correct way to handle this is for `Gradle.DescriptorImpl` (and other) to populate `GradleInstallation$DescriptorImpl#installations` when some legacy data get loaded, and fully follow the intended `ToolDescriptor` design to host `installations` property.

          Stefan Wolf added a comment -

          ndeloof: Do you have an example of a plugin doing the data migration? Do I need to do anything else than populating the installations field of GradleInstallation? For example, I wonder how the version numbers to select from for the GradleInstaller are populated, and if this is related to the GradleInstallation. I vaguely remember there is some backend job which provides the versions which can be accessed via some class name. 

          Stefan Wolf added a comment - ndeloof : Do you have an example of a plugin doing the data migration? Do I need to do anything else than populating the installations field of GradleInstallation ? For example, I wonder how the version numbers to select from for the GradleInstaller are populated, and if this is related to the GradleInstallation . I vaguely remember there is some backend job which provides the versions which can be accessed via some class name. 

          Stefan Wolf added a comment -

          Fixed in the Gradle plugin 1.30.

          Stefan Wolf added a comment - Fixed in the Gradle plugin 1.30.

            wolfs Stefan Wolf
            jnz_topdanmark Jon Brohauge
            Votes:
            2 Vote for this issue
            Watchers:
            7 Start watching this issue

              Created:
              Updated:
              Resolved: