Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add api support to PluginUsageView #18

Merged
merged 2 commits into from
Feb 16, 2021
Merged

Conversation

sghill
Copy link
Contributor

@sghill sghill commented Dec 16, 2020

Hi,

Thanks for this plugin. It helps determine which plugins can be easily removed. Would it be possible to add api support?

My use case is I have several Jenkins controllers with different plugins that I'd like to standardize. As a first pass, I'm checking which plugins are installed but not used by any jobs in order to uninstall them. Having api support would allow easily checking which plugins are not used in jobs without visiting the UI on each controller.

Below is an example of what this change will serve from /pluginusage/api/json?depth=2.

{
  "_class": "org.jenkinsci.plugins.pluginusage.PluginUsageModel",
  "jobsPerPlugin": [
    {
      "plugin": {
        "active": true,
        "backupVersion": null,
        "bundled": false,
        "deleted": false,
        "dependencies": [
          {},
          {},
          {}
        ],
        "downgradable": false,
        "enabled": true,
        "hasUpdate": true,
        "longName": "Branch API Plugin",
        "pinned": false,
        "requiredCoreVersion": "1.642.3",
        "shortName": "branch-api",
        "supportsDynamicLoad": "MAYBE",
        "url": "http://wiki.jenkins-ci.org/display/JENKINS/Branch+API+Plugin",
        "version": "1.11"
      },
      "projects": [
        {
          "_class": "hudson.model.FreeStyleProject",
          "actions": [
            {},
            {},
            {},
            {}
          ],
          "description": "",
          "displayName": "abcd",
          "displayNameOrNull": null,
          "fullDisplayName": "abcd",
          "fullName": "abcd",
          "name": "abcd",
          "url": "http://localhost:8080/jenkins/job/abcd/",
          "buildable": true,
          "builds": [
            {
              "_class": "hudson.model.FreeStyleBuild",
              "number": 1,
              "url": "http://localhost:8080/jenkins/job/abcd/1/"
            }
          ],
          "color": "red",
          "firstBuild": {
            "_class": "hudson.model.FreeStyleBuild",
            "number": 1,
            "url": "http://localhost:8080/jenkins/job/abcd/1/"
          },
          "healthReport": [
            {
              "description": "Build stability: All recent builds failed.",
              "iconClassName": "icon-health-00to19",
              "iconUrl": "health-00to19.png",
              "score": 0
            }
          ],
          "inQueue": false,
          "keepDependencies": false,
          "lastBuild": {
            "_class": "hudson.model.FreeStyleBuild",
            "number": 1,
            "url": "http://localhost:8080/jenkins/job/abcd/1/"
          },
          "lastCompletedBuild": {
            "_class": "hudson.model.FreeStyleBuild",
            "number": 1,
            "url": "http://localhost:8080/jenkins/job/abcd/1/"
          },
          "lastFailedBuild": {
            "_class": "hudson.model.FreeStyleBuild",
            "number": 1,
            "url": "http://localhost:8080/jenkins/job/abcd/1/"
          },
          "lastStableBuild": null,
          "lastSuccessfulBuild": null,
          "lastUnstableBuild": null,
          "lastUnsuccessfulBuild": {
            "_class": "hudson.model.FreeStyleBuild",
            "number": 1,
            "url": "http://localhost:8080/jenkins/job/abcd/1/"
          },
          "nextBuildNumber": 2,
          "property": [
            {
              "_class": "jenkins.branch.RateLimitBranchProperty$JobPropertyImpl"
            }
          ],
          "queueItem": null,
          "concurrentBuild": false,
          "downstreamProjects": [],
          "labelExpression": null,
          "scm": {
            "_class": "hudson.scm.NullSCM"
          },
          "upstreamProjects": []
        }
      ]
    }
  ]
}

It also supports the tree query parameter to customize what is returned. Here is an example for /pluginusage/api/json?tree=jobsPerPlugin[plugin[shortName],projects[fullName]]:

{
  "_class": "org.jenkinsci.plugins.pluginusage.PluginUsageModel",
  "jobsPerPlugin": [
    {
      "plugin": {
        "shortName": "branch-api"
      },
      "projects": [
        {
          "_class": "hudson.model.FreeStyleProject",
          "fullName": "abcd"
        }
      ]
    },
    {
      "plugin": {
        "shortName": "docker-workflow"
      },
      "projects": []
    }
  ]
}

Allow the exported data in PluginUsageModel and JobsPerPlugin to be
served over the json/xml api.
@sghill
Copy link
Contributor Author

sghill commented Jan 4, 2021

Hi @froque, would you be in favor of adding an api to this plugin?

@froque
Copy link
Member

froque commented Jan 5, 2021

Hi @sghill.

This is a fine addition.

I didn't get any email for the pull request creation, so sorry for the delay.

Why not also exposing getOtherPlugins in PluginUsageModel ?

My only concern is that we now have an API to maintain and will be harder to detect breaking changes.

Do you think you can create a unit test with some basic information: plugin name, plugin version, job name ?

@sghill
Copy link
Contributor Author

sghill commented Jan 7, 2021

No worries, thanks for the quick response here.

Why not also exposing getOtherPlugins in PluginUsageModel ?

I didn't have a need for it, but can add the annotation if you'd prefer. The built-in plugin manager does have an api that can be used to get installed plugins. Here's an example of /pluginManager/api/json?depth=2:

{
    "_class": "hudson.LocalPluginManager",
    "plugins": [
        {
            "active": true,
            "backupVersion": "2.11.2",
            "bundled": false,
            "deleted": false,
            "dependencies": [
                {
                    "optional": false,
                    "shortName": "snakeyaml-api",
                    "version": "1.26.4"
                }
            ],
            "detached": false,
            "downgradable": true,
            "enabled": true,
            "hasUpdate": true,
            "longName": "Jackson 2 API Plugin",
            "minimumJavaVersion": "1.8",
            "pinned": false,
            "requiredCoreVersion": "2.222.4",
            "shortName": "jackson2-api",
            "supportsDynamicLoad": "YES",
            "url": "https://plugins.jenkins.io/jackson2-api",
            "version": "2.11.3"
        }
   ]
}

Do you think you can create a unit test with some basic information: plugin name, plugin version, job name ?

I like this idea. I've been trying to create a test, but it's more difficult than expected. I think it comes down the TestPluginManager not being aware of any installed plugins, so the test is going to have to install them first. I'll keep going down this path, and hopefully will have something soon.

This test ensures the ant plugin and its transitives are installed
before running. The job is created through XML and the api response is
deserialized into classes that mirror the expected structure of the
response. A change in model structure would cause this test to fail.

It's important to create the job through XML instead of creating a
project through the JenkinsRule with an Ant builder. If the ant
plugin's classes are used directly, the plugin manager detects
`hudson.tasks.Ant` was loaded through the test's class loader instead
of the plugin's class loader - which results in the plugin under test
not finding the ant plugin.

These versions of ant and structs were chosen because they're the most
recent that are compatible with the version of Jenkins core.
@sghill
Copy link
Contributor Author

sghill commented Jan 30, 2021

Hi @froque,

I added a test and some supporting classes for deserialization that ensures only the correct jobs are returned for an example plugin. Is this aligned with what you had in mind?

I tried a couple things before landing on this implementation --

@WithPlugin

First I added the ant plugin as a test dependency and created a job with a hudson.tasks.Ant builder. I was hopeful this would be enough after reading this in the javadoc for @WithPlugin:

In most cases you do not need this annotation.
Simply add whatever plugins you are interested in testing against to your POM in test scope.

Unfortunately that doesn't work out here. PluginManager#whichPlugin(Class) iterates all the active plugins. If the plugin is only available on the classpath, it isn't considered active.

Creating Jobs

I tried to use the JenkinsRule#createFreeStyleJob(String) api to create the jobs for the test with the hudson.tasks.Ant builder, but noticed that it this meant the plugin class was not loaded by its own class loader, so the plugin never returned the correct values.

Creating the job by XML and not having the ant classes on the test classpath resolved this issue.

@sghill
Copy link
Contributor Author

sghill commented Feb 8, 2021

Hi @froque, are there any changes you'd like me to make to this PR?

@froque
Copy link
Member

froque commented Feb 16, 2021

everything looks fine

@froque froque merged commit 3918371 into jenkinsci:master Feb 16, 2021
@sghill sghill deleted the add-api branch February 16, 2021 17:33
@sghill
Copy link
Contributor Author

sghill commented Feb 16, 2021

Thank you!

@kad-meedel
Copy link

Is there an api call present to list the number of jobs a plugin is being used?
I can only see the /pluginManager/api/json?depth=1 or 2 which lists the settings of the plugins, but not the usage.
The graphical interface shows this information.

@sghill
Copy link
Contributor Author

sghill commented Sep 7, 2021

Hi @kad-meedel. It's exposing the existing data structure which does include this information as you mentioned. Here's an example of turning this response into a usage count (subject to all the same caveats as the UI version):

/pluginusage/api/json?tree=jobsPerPlugin[plugin[shortName],projects[fullName]]
// response from request above
const json = {
  "_class": "org.jenkinsci.plugins.pluginusage.PluginUsageModel",
  "jobsPerPlugin": [
    {
      "plugin": {
        "shortName": "branch-api"
      },
      "projects": [
        {
          "_class": "hudson.model.FreeStyleProject",
          "fullName": "abcd"
        }
      ]
    },
    {
      "plugin": {
        "shortName": "docker-workflow"
      },
      "projects": []
    }
  ]
};

// count jobs by plugin
const jobCountByPlugin = json.jobsPerPlugin.reduce((acc, x) => {
    acc[x.plugin.shortName] = x.projects.length;
    return acc;
}, {});

// returns { "branch-api": 1, "docker-workflow": 0 }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants