How to create a view programmatically via init.groovy.d?

I am trying to create views programmatically when a Jenkins instance starts up.
We have a very automated cattle approach to our instances, and thus there is no “admin access” to customize an instance once it has been created.

My groovy code that implements the “create custom views” is (init.groovy.d/A998AddCustomViews.groovy):

import jenkins.model.Jenkins
import hudson.model.ListView
import java.util.logging.Logger
import org.yaml.snakeyaml.Yaml

Logger logger = Logger.getLogger('Custom Views')

logger.info("Custom Views -> Downloading custom views")

Yaml yaml = new Yaml()

String virtualHost = System.getenv('VIRTUAL_HOST')
virtualHost = virtualHost.toLowerCase()
String[] hostName = virtualHost.split('<REDACTED FQDN>')

String githubUrl = 'https://<URL to views.yaml>'
String viewsFile = "${githubUrl}/${hostName[0]}/views.yaml"
logger.info('Custom Views -> viewsFile URL: ' + viewsFile)

try {
    logger.info('Custom Views -> Loading contents of views.yaml')
    String viewsYaml = new URL(viewsFile).getText()
    Map viewsMap = (Map) yaml.load(viewsYaml)
    if (viewsMap.containsKey('jenkins')) {
        Map tmpMap = viewsMap.jenkins

        if (tmpMap.size() == 1 && tmpMap.containsKey('views')) {
            logger.debug('Custom Views -> views.yaml contains views key')
            List viewsList = tmpMap.views

            instance = Jenkins.getInstanceOrNull()

            instance.getViews().each { view ->
                logger.info("Custom Views -> Existing view ${view.name}")
            }
            viewsList.each { input ->
                input.each { key, value ->
                    logger.info("Custom Views -> Creating view ${value.name}")
                    switch (key) {
                        case "list":
                            ListView listView = new ListView(value.name)
                            listView.setColumns(value.columns)
                            listView.setJobNames((value.jobNames).flatten() as Set)
                            listView.setRecurse(value.recurse)
                            instance.addView(listView)
                            break
                        case "buildMonitor":
                            logger.info('Custom Views -> BuildMonitorView not supported yet')
                            break
                        case "myView":
                            logger.info('Custom Views -> myView not supported yet')
                            break
                    }
                    logger.info("Custom Views -> Get Views: ${instance.getViews()}")
                }
            }
            logger.info('Custom Views -> Saving views')
            instance.save()
            logger.info("Custom Views -> Get all views: ${instance.getViews()}")
        } else {
            logger.severe('Custom Views -> views.yaml not formatted correctly!')
            logger.severe('  -> Missing views or contains other keys - Aborting load')
        }
    } else {
        logger.severe('Custom Views -> views.yaml not formatted correctly!')
        logger.severe('  -> Missing jenkins - Aborting load')
    }
} catch (FileNotFoundException e) {
    logger.info('Custom Views -> No customization file found')
} catch (IllegalStateException e) {
    logger.severe("Custom Views -> Exception caught: ${e.exception()}")
}

An example of a views.yaml file:

---
jenkins:
  views:
    - list:
        name: "viewList1"
        columns:
        - "status"
        - "weather"
        - "jobName"
        - "lastSuccess"
        - "lastFailure"
        - "lastDuration"
        - "buildButton"
        - "favoriteColumn"
        jobNames:
        - "jenkins-ci-test/aws-bundle-sample"
        - "jenkins-ci-test/currentBuild.Result"
        - "jenkins-ci-test/java-ee-sampleapp"
        - "jenkins-ci-test/module-sample-app"
        - "jenkins-ci-test/multiple-docker-builds"
        - "jenkins-ci-test/multiple-java-installations"
        - "jenkins-ci-test/multiple-nodejs-installations"
        - "jenkins-ci-test/nytland-ping-test"
        recurse: true
    - list:
        name: "viewList2"
        columns:
        - "status"
        - "weather"
        - "jobName"
        jobNames:
        - "jenkins-ci-test/autotaks-inbox-monitoring-lambda"
        - "jenkins-ci-test/aws-bundle-sample-internal"
        recurse: true
    - buildMonitor:
        name: "buildMonitor1"
        includeRegex: ".*"
        recurse: true
        title: "buildMonitor1"
    - buildMonitor:
        name: "buildMonitor2"
        includeRegex: ".*sample.*"
        recurse: true
        title: "buildMonitor2"
    - myView:
        description: "test hset"
        name: "myView1"

The log tells me everything went well, however when I access the instance, there are no added views.

Any help will be appreciated.

Log output:

2023-05-04 08:25:41.974+0000 [id=30]    INFO    j.util.groovy.GroovyHookScript#execute: Executing /var/jenkins_home/init.groovy.d/A998AddCustomViews.groovy
2023-05-04 08:25:42.174+0000 [id=30]    INFO    java_util_logging_Logger$info$0#call: Custom Views -> Downloading custom views
2023-05-04 08:25:42.176+0000 [id=30]    INFO    java_util_logging_Logger$info$0#call: Custom Views -> viewsFile URL: https://<URL to views.yaml>/views.yaml
2023-05-04 08:25:42.176+0000 [id=30]    INFO    java_util_logging_Logger$info$0#call: Custom Views -> Loading contents of views.yaml
2023-05-04 08:25:42.312+0000 [id=30]    INFO    java_util_logging_Logger$info$0#call: Custom Views -> views.yaml contains views key
2023-05-04 08:25:42.317+0000 [id=30]    INFO    o.c.g.r.c.PojoMetaMethodSite$PojoCachedMethodSiteNoUnwrap#invoke: Custom Views -> Existing view all
2023-05-04 08:25:42.318+0000 [id=30]    INFO    o.c.g.r.c.PojoMetaMethodSite$PojoCachedMethodSiteNoUnwrap#invoke: Custom Views -> Creating view viewList1
2023-05-04 08:25:42.344+0000 [id=30]    INFO    o.c.g.r.c.PojoMetaMethodSite$PojoCachedMethodSiteNoUnwrap#invoke: Custom Views -> Get Views: [hudson.model.AllView@25544e98[view/all/], hudson.model.ListView@2cc9a4fb[view/viewList1/]]
2023-05-04 08:25:42.344+0000 [id=30]    INFO    o.c.g.r.c.PojoMetaMethodSite$PojoCachedMethodSiteNoUnwrap#invoke: Custom Views -> Creating view viewList2
2023-05-04 08:25:42.352+0000 [id=30]    INFO    o.c.g.r.c.PojoMetaMethodSite$PojoCachedMethodSiteNoUnwrap#invoke: Custom Views -> Get Views: [hudson.model.AllView@25544e98[view/all/], hudson.model.ListView@2cc9a4fb[view/viewList1/], hudson.model.ListView@55020e7a[view/viewList2/]]
2023-05-04 08:25:42.352+0000 [id=30]    INFO    o.c.g.r.c.PojoMetaMethodSite$PojoCachedMethodSiteNoUnwrap#invoke: Custom Views -> Creating view buildMonitor1
2023-05-04 08:25:42.353+0000 [id=30]    INFO    o.c.g.r.c.PojoMetaMethodSite$PojoCachedMethodSiteNoUnwrap#invoke: Custom Views -> buildMonitor -> [name:buildMonitor1, includeRegex:.*, recurse:true, title:buildMonitor1]
2023-05-04 08:25:42.353+0000 [id=30]    INFO    java_util_logging_Logger$info$0#call: Custom Views -> BuildMonitorView not supported yet
2023-05-04 08:25:42.353+0000 [id=30]    INFO    o.c.g.r.c.PojoMetaMethodSite$PojoCachedMethodSiteNoUnwrap#invoke: Custom Views -> Get Views: [hudson.model.AllView@25544e98[view/all/], hudson.model.ListView@2cc9a4fb[view/viewList1/], hudson.model.ListView@55020e7a[view/viewList2/]]
2023-05-04 08:25:42.353+0000 [id=30]    INFO    o.c.g.r.c.PojoMetaMethodSite$PojoCachedMethodSiteNoUnwrap#invoke: Custom Views -> Creating view buildMonitor2
2023-05-04 08:25:42.353+0000 [id=30]    INFO    o.c.g.r.c.PojoMetaMethodSite$PojoCachedMethodSiteNoUnwrap#invoke: Custom Views -> buildMonitor -> [name:buildMonitor2, includeRegex:.*sample.*, recurse:true, title:buildMonitor2]
2023-05-04 08:25:42.354+0000 [id=30]    INFO    java_util_logging_Logger$info$0#call: Custom Views -> BuildMonitorView not supported yet
2023-05-04 08:25:42.354+0000 [id=30]    INFO    o.c.g.r.c.PojoMetaMethodSite$PojoCachedMethodSiteNoUnwrap#invoke: Custom Views -> Get Views: [hudson.model.AllView@25544e98[view/all/], hudson.model.ListView@2cc9a4fb[view/viewList1/], hudson.model.ListView@55020e7a[view/viewList2/]]
2023-05-04 08:25:42.354+0000 [id=30]    INFO    o.c.g.r.c.PojoMetaMethodSite$PojoCachedMethodSiteNoUnwrap#invoke: Custom Views -> Creating view myView1
2023-05-04 08:25:42.354+0000 [id=30]    INFO    java_util_logging_Logger$info$0#call: Custom Views -> myView not supported yet
2023-05-04 08:25:42.354+0000 [id=30]    INFO    o.c.g.r.c.PojoMetaMethodSite$PojoCachedMethodSiteNoUnwrap#invoke: Custom Views -> Get Views: [hudson.model.AllView@25544e98[view/all/], hudson.model.ListView@2cc9a4fb[view/viewList1/], hudson.model.ListView@55020e7a[view/viewList2/]]
2023-05-04 08:25:42.354+0000 [id=30]    INFO    java_util_logging_Logger$info$0#call: Custom Views -> Saving views
2023-05-04 08:25:42.362+0000 [id=30]    INFO    o.c.g.r.c.PojoMetaMethodSite$PojoCachedMethodSiteNoUnwrap#invoke: Custom Views -> Get all views: [hudson.model.AllView@25544e98[view/all/], hudson.model.ListView@2cc9a4fb[view/viewList1/], hudson.model.ListView@55020e7a[view/viewList2/]]

Are you using JobDSL to load pipeline jobs at run time? I would just use JobDSL to create my views.

It looks like the views are being created, does your user have access to view this view?

Yes, we are exclusively using JobDSL to execute whatever job is required to run.

I’m not sure how your would execute JobDSL at the initialization stage. The primary reason for the above groovy-“magic/hack/code” is that the buildMonitorView is not fully JCasC compatible.

I just cannot get a grip on why I can add a cloud configuration (including agent-templates) with a:
“Load running instance” → “Add cloud configuration” → “Save instance”, but the similar approach does not work properly with views.

As I see it, it’s basically the same thing that happens, which is a manipulation of an entry in config.xml, albeit via Hudson/Jenkins methods.