Skip to content

Commit

Permalink
[JENKINS-46162] Support workflow/pipeline linting (#30)
Browse files Browse the repository at this point in the history
* Supporting pipeline jobs, fixed some bugs, bumped minimal version to allow the usage of the pipelines

* Supporting GroovySandbox checker only in Jenkinsfiles, aka WorkflowJobs

* [JENKINS-46162] Support Pipeline project to be linted

* Added TestCases for the Pipeline jobs

* Enable System Groovy detection

* Missing testcases

* Enabling JenkinsLint action for pipelines/workflows

Change-Id: Ideaa90900b836d2a2f115fc7eefa7c2ab5533197

* Support pipeline timer trigger check

Change-Id: I857d85210a0f95c22226798320508132cecaf9fa

* Fixed TimeoutChecker for pipeline (some false positives)

Change-Id: I2f9cdd04a73702ffa16cb7050123dc6a63fb759b

* Support pipeline PollingSCMtrigger check

Change-Id: I7ef1a3228096c2e43713495629e044bbb141d898
  • Loading branch information
v1v committed Aug 14, 2017
1 parent 07b8bc8 commit 2b5910a
Show file tree
Hide file tree
Showing 40 changed files with 399 additions and 84 deletions.
16 changes: 15 additions & 1 deletion pom.xml
Expand Up @@ -11,10 +11,11 @@
</parent>

<properties>
<jenkins.version>1.580</jenkins.version>
<jenkins.version>1.607</jenkins.version>
<jenkins-test-harness.version>1.580</jenkins-test-harness.version>
<!--TODO: make true after the code cleanup-->
<findbugs.failOnError>false</findbugs.failOnError>
<workflow.version>1.14.2</workflow.version>
</properties>

<artifactId>jenkinslint</artifactId>
Expand Down Expand Up @@ -195,6 +196,19 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-job</artifactId>
<version>${workflow.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-cps</artifactId>
<version>${workflow.version}</version>
<scope>test</scope>
</dependency>
<!-- JENKINS-29427 -->
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
Expand Down
Expand Up @@ -29,14 +29,13 @@ public void getData() throws IOException {
this.reloadCheckList();
this.reloadSlaveCheckList();

for (AbstractProject item : Jenkins.getInstance().getAllItems(AbstractProject.class)) {
LOG.log(Level.FINER, "queryChecks " + item.getDisplayName());
for (hudson.model.Job item : Jenkins.getInstance().getAllItems(hudson.model.Job.class)) {
LOG.log(Level.FINER, "queryChecks " + item.getName());
Job newJob = new Job(item.getName(), item.getUrl());
for (InterfaceCheck checker : this.getCheckList()) {
LOG.log(Level.FINER, checker.getName() + " " + item.getName() + " " + checker.executeCheck(item));
// Lint is disabled when is ignored or globally disabled
newJob.addLint(new Lint(checker.getName(), checker.executeCheck(item), checker.isIgnored(item.getDescription()), checker.isEnabled()));

}
jobSet.put(item.getName(),newJob);
LOG.log(Level.FINER, newJob.toString());
Expand Down
Expand Up @@ -44,6 +44,8 @@ public final class JenkinsLintGlobalConfiguration extends GlobalConfiguration {
private boolean timerTriggerCheckerEnabled = true;
private boolean windowsSlaveLaunchCheckerEnabled = true;

private boolean groovySandboxCheckerEnabled = true;

private int hardcodedScriptThreshold = HardcodedScriptChecker.THRESHOLD;
private boolean hardcodedScriptIgnoredComment = false;

Expand Down Expand Up @@ -302,6 +304,14 @@ public void setHardcodedScriptIgnoredComment(boolean hardcodedScriptIgnoredComme
this.hardcodedScriptIgnoredComment = hardcodedScriptIgnoredComment;
}

public boolean isGroovySandboxCheckerEnabled() {
return groovySandboxCheckerEnabled;
}

public void setWorkflowSandboxCheckerEnabled(boolean groovySandboxCheckerEnabled) {
this.groovySandboxCheckerEnabled = groovySandboxCheckerEnabled;
}

/**
* Performs on-the-fly validation of the form field 'name'.
*
Expand Down
@@ -1,7 +1,8 @@
package org.jenkins.ci.plugins.jenkinslint;

import hudson.model.AbstractProject;
import hudson.Extension;
import hudson.model.Action;
import jenkins.model.TransientActionFactory;
import org.jenkins.ci.plugins.jenkinslint.model.AbstractAction;
import org.jenkins.ci.plugins.jenkinslint.model.InterfaceCheck;
import org.jenkins.ci.plugins.jenkinslint.model.Job;
Expand All @@ -10,25 +11,27 @@
import org.kohsuke.stapler.export.ExportedBean;

import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.logging.Level;
import java.util.logging.Logger;

@ExportedBean
public final class JobLintAction extends AbstractAction implements Action {

private static final Logger LOG = Logger.getLogger(JobLintAction.class.getName());
private AbstractProject<?, ?> project;
private hudson.model.Job project;
private Job job;

public static boolean isDisabled () {
return !JenkinsLintGlobalConfiguration.get().isJobActionEnabled();
}

public JobLintAction(AbstractProject<?, ?> project) {
public JobLintAction(hudson.model.Job project) {
this.project = project;
}

public AbstractProject<?, ?> getProject() {
public final hudson.model.Job<?, ?> getProject() {
return project;
}

Expand All @@ -38,7 +41,6 @@ public Job getJob() {
}

public void getData() throws IOException {

this.reloadCheckList();
this.job = new Job(this.project.getName(), this.project.getUrl());
for (InterfaceCheck checker : this.getCheckList()) {
Expand All @@ -47,5 +49,16 @@ public void getData() throws IOException {
LOG.log(Level.FINE, this.job.getLintList().toString());
}

@Extension
public static class Factory extends TransientActionFactory<hudson.model.Job> {
@Override
public Class<hudson.model.Job> type() {
return hudson.model.Job.class;
}

@Override
public Collection<? extends Action> createFor(hudson.model.Job target) {
return Collections.singleton(new JobLintAction(target));
}
}
}

This file was deleted.

@@ -1,7 +1,9 @@
package org.jenkins.ci.plugins.jenkinslint;

import hudson.Extension;
import hudson.model.Action;
import hudson.model.Computer;
import hudson.model.TransientComputerActionFactory;
import org.jenkins.ci.plugins.jenkinslint.model.AbstractAction;
import org.jenkins.ci.plugins.jenkinslint.model.InterfaceSlaveCheck;
import org.jenkins.ci.plugins.jenkinslint.model.Lint;
Expand All @@ -10,6 +12,8 @@
import org.kohsuke.stapler.export.ExportedBean;

import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.logging.Level;
import java.util.logging.Logger;

Expand Down Expand Up @@ -43,4 +47,11 @@ public void getData() throws IOException {
}
}

@Extension
public static class Factory extends TransientComputerActionFactory {
@Override
public Collection<? extends Action> createFor(Computer target) {
return Collections.singleton(new SlaveLintAction(target));
}
}
}

This file was deleted.

@@ -0,0 +1,50 @@
package org.jenkins.ci.plugins.jenkinslint.check;

import hudson.model.Item;
import hudson.model.Job;
import org.jenkins.ci.plugins.jenkinslint.model.AbstractCheck;

import java.util.logging.Level;

/**
* @author Victor Martinez
*/
public class GroovySandboxChecker extends AbstractCheck {

public GroovySandboxChecker(boolean enabled) {
super(enabled);
this.setDescription(Messages.GroovySandboxCheckerDesc());
this.setSeverity(Messages.GroovySandboxCheckerSeverity());
}

public boolean executeCheck(Item item) {
LOG.log(Level.FINE, "executeCheck " + item);
if (item instanceof Job) {
// Pipeline support
if (item.getClass().getSimpleName().equals("WorkflowJob")) {
try {
Object getDefinition = item.getClass().getMethod("getDefinition", null).invoke(item);
if (getDefinition.getClass().getSimpleName().equals("CpsFlowDefinition")) {
return !isSandbox(getDefinition);
}
} catch (Exception e) {
LOG.log(Level.FINE, "Exception " + e.getMessage(), e.getCause());
}
}
}
return false;
}

private boolean isSandbox(Object object) {
boolean status = true;
if (object != null) {
try {
Object isSandbox = object.getClass().getMethod("isSandbox", null).invoke(object);
return ((Boolean) isSandbox);
} catch (Exception e) {
LOG.log(Level.WARNING, "Exception " + e.getMessage(), e.getCause());
}
}
return status;
}
}
Expand Up @@ -3,8 +3,7 @@
import hudson.model.AbstractProject;
import hudson.model.Descriptor;
import hudson.model.Item;
import hudson.model.JobProperty;
import hudson.model.JobPropertyDescriptor;
import hudson.model.Job;
import hudson.model.ParameterDefinition;
import hudson.model.ParametersDefinitionProperty;
import hudson.model.Project;
Expand All @@ -14,7 +13,6 @@
import org.jenkins.ci.plugins.jenkinslint.model.AbstractCheck;

import java.util.List;
import java.util.Map;
import java.util.logging.Level;

/**
Expand Down Expand Up @@ -54,13 +52,31 @@ public boolean executeCheck(Item item) {
}
}

if (isSystemExitInPublisher(((AbstractProject) item).getPublishersList())) {
found = true;
if (item instanceof AbstractProject) {
if (isSystemExitInPublisher(((AbstractProject) item).getPublishersList())) {
found = true;
}

if (((AbstractProject) item).getProperty(ParametersDefinitionProperty.class) != null) {
if (isSystemExitInParameters(((ParametersDefinitionProperty) ((AbstractProject) item).getProperty(ParametersDefinitionProperty.class)).getParameterDefinitions())) {
found = true;
}
}
}

if (((AbstractProject) item).getProperty(ParametersDefinitionProperty.class)!=null) {
if (isSystemExitInParameters(((ParametersDefinitionProperty)((AbstractProject) item).getProperty(ParametersDefinitionProperty.class)).getParameterDefinitions())) {
found = true;
if (item instanceof Job) {
// Pipeline support
if (item.getClass().getSimpleName().equals("WorkflowJob")) {
try {
Object getDefinition = item.getClass().getMethod("getDefinition", null).invoke(item);
if (getDefinition.getClass().getSimpleName().equals("CpsFlowDefinition")) {
if (containsSystemExit(getDefinition.getClass().getMethod("getScript",null).invoke(getDefinition))) {
found = true;
}
}
} catch (Exception e) {
LOG.log(Level.FINE, "Exception " + e.getMessage(), e.getCause());
}
}
}
return found;
Expand Down
Expand Up @@ -4,6 +4,9 @@
import hudson.model.Item;
import org.jenkins.ci.plugins.jenkinslint.model.AbstractCheck;

import java.lang.reflect.Method;
import java.util.logging.Level;

/**
* @author Victor Martinez
*/
Expand All @@ -17,9 +20,22 @@ public JobDescriptionChecker(boolean enabled) {

public boolean executeCheck(Item item) {
if (item instanceof AbstractItem) {
return (((AbstractItem) item).getDescription() == null
|| ((AbstractItem) item).getDescription().length() == 0);
return isDescription(((AbstractItem) item).getDescription());
}
if (item.getClass().getSimpleName().equals("WorkflowJob")) {
try {
Object getDescription = item.getClass().getMethod("getDescription", null).invoke(item);
if (getDescription instanceof String) {
return isDescription((String) getDescription);
}
} catch (Exception e) {
LOG.log(Level.FINE, "Exception " + e.getMessage(), e.getCause());
}
}
return false;
}

private boolean isDescription(String description) {
return (description == null || description.length() == 0);
}
}
Expand Up @@ -5,6 +5,9 @@
import hudson.triggers.SCMTrigger;
import org.jenkins.ci.plugins.jenkinslint.model.AbstractCheck;

import java.util.Map;
import java.util.logging.Level;

/**
* @author Victor Martinez
*/
Expand All @@ -17,9 +20,24 @@ public PollingSCMTriggerChecker(boolean enabled) {
}

public boolean executeCheck(Item item) {
boolean found = false;
if (item instanceof AbstractProject) {
return (((AbstractProject) item).getTrigger(SCMTrigger.class) != null);
found = (((AbstractProject) item).getTrigger(SCMTrigger.class) != null);
}
if (item.getClass().getSimpleName().equals("WorkflowJob")) {
try {
Object getTriggers = item.getClass().getMethod("getTriggers", null).invoke(item);
if (getTriggers instanceof Map) {
for (Object value : ((Map) getTriggers).values()) {
if (value instanceof SCMTrigger) {
found = true;
}
}
}
} catch (Exception e) {
LOG.log(Level.FINE, "Exception " + e.getMessage(), e.getCause());
}
}
return false;
return found;
}
}

0 comments on commit 2b5910a

Please sign in to comment.