Skip to content

Commit b6ea464

Browse files
committed
initial commit
0 parents  commit b6ea464

13 files changed

+1168
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
*.pyc
2+
.idea
3+
*.iml
4+
*.retry

README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Jenkins Master Configuration
2+
This repo is used to build a customized OpenShift Jenkins 2 image with [source to image (S2I)](https://github.com/openshift/source-to-image). The base OpenShift Jenkins S2I can be found at `registry.access.redhat.com/openshift3/jenkins-2-rhel7`. The resulting image is a Jenkins master, and should be used in a master / slaves architecture. This image is configured to provide slaves as k8s pods via the [k8s Jenkins plugin](https://docs.openshift.com/container-platform/3.5/using_images/other_images/jenkins.html#using-the-jenkins-kubernetes-plug-in-to-run-jobs). Thus, this repo doesn't define any build tools or the like, as they are the responsibility of the slaves.
3+
4+
## Building and Testing Locally
5+
With `s2i` installed; you can run the following to build and test your changes to the S2I locally.
6+
```bash
7+
s2i build --loglevel 5 jenkins-master openshift/jenkins-2-centos7 jenkins-s2i:latest
8+
```
9+
10+
## How This Repo Works
11+
12+
The directory structure is dictated by [OpenShift Jenkins S2I image](https://docs.openshift.com/container-platform/3.5/using_images/other_images/jenkins.html#jenkins-as-s2i-builder). In particular:
13+
14+
- [plugins.txt](plugins.txt) is used to install plugins during the S2I build. If you want the details, here is the [S2I assemble script](https://github.com/openshift/jenkins/blob/master/2/contrib/s2i/assemble), which calls the [install jenkins plugins script](https://github.com/openshift/jenkins/blob/master/2/contrib/jenkins/install-plugins.sh).
15+
- files in the [configuration](configuration) directory will have comments describing exactly what they do
16+
17+
## Slack Integration
18+
19+
To Integrate with slack follow the steps at https://github.com/jenkinsci/slack-plugin. Particularly, create a webhook at https://customteamname.slack.com/services/new/jenkins-ci. After the webhook setup is complete at slack, record and add the below environmental variables. You can retrieve the values on your [slack dashboard](https://my.slack.com/services/new/jenkins-ci). Make sure you are logged into the correct team.
20+
1. The base url as `SLACK_BASE_URL`
21+
2. The slack token as `SLACK_TOKEN`
22+
3. The slack room you selected as the default slack channel as `SLACK_ROOM`
23+
4. optionally, a jenkins credential can be used for the token and referenced by a custom id at `SLACK_TOKEN_CREDENTIAL_ID`. This takes precedences over the `SLACK_TOKEN`
24+
25+
## SonarQube Integration
26+
27+
By default the deployment will attempt to connect to SonarQube and configure its setup including an authentication token. The default url is http://sonarqube:9000. This can be overriden adding an environment variable named `SONARQUBE_URL`. To disable SonarQube entirely set an environment variable named `DISABLE_SONAR` with any value.
28+
29+
## Git Creds
30+
Inject the `git` credentials to Jenkins-s2i when it is being built by editing `configuration/init.groovy.d/configure-credentials.groovy` or by exposing a new environment Variable to the Jenkins deployment tempate.
31+
32+
## Shared Library
33+
34+
An optional shared global library can be used to add method calls to pipelines which can help to simplify and organize a pipeline. The global library will be implicitly available to all pipelines.
35+
36+
To configure a library environment variables need to be made available to your image. In OCP, add environment variables to your deployment config. The following variables can be set
37+
1. SHARED_LIB_REPO - If this variable is set then the deployment will attempt to configure a shared global library. This value should reference a git repository. If this value is not set, no shared global library will be set.
38+
2. SHARED_LIB_REF - A value that that points to a git reference such as a branch or tag of a repository. The default value is `master`
39+
3. SHARED_LIB_NAME - A name for the library. It can be anything.
40+
4. SHARED_LIB_SECRET - If the git repo is private, this value should be a reference to a secret available to the project. If this value is not set, it is assumed that the git repo is publicly available. This value assumes a deployment on openshift so it prepends that value of the namespace to the secret.
41+
42+
## Contributing
43+
44+
There are some [helpers](helpers/README.MD) to get configuration out of a running Jenkins.
45+

configuration/build-failure-analyzer.xml

Lines changed: 475 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#!/usr/bin/env groovy
2+
import jenkins.model.*
3+
import com.cloudbees.plugins.credentials.*
4+
import com.cloudbees.plugins.credentials.common.*
5+
import com.cloudbees.plugins.credentials.domains.*
6+
import com.cloudbees.plugins.credentials.impl.*
7+
8+
import jenkins.*
9+
import hudson.model.*
10+
import hudson.security.*
11+
12+
import java.util.logging.Level
13+
import java.util.logging.Logger
14+
15+
final def LOG = Logger.getLogger("LABS")
16+
17+
LOG.log(Level.INFO, 'running configure-credentials.groovy' )
18+
19+
// create jenkins creds for commiting tags back to repo. Can use Env vars on the running image or just insert below.
20+
domain = Domain.global()
21+
store = Jenkins.instance.getExtensionList('com.cloudbees.plugins.credentials.SystemCredentialsProvider')[0].getStore()
22+
gitUsername = System.getenv("GIT_USERNAME") ?: "jenkins-user"
23+
gitPassword = System.getenv("GIT_PASSWORD") ?: "password-for-user"
24+
usernameAndPassword = new UsernamePasswordCredentialsImpl(
25+
CredentialsScope.GLOBAL,
26+
"jenkins-git-creds", "Git creds for Jenkins",
27+
gitUsername,
28+
gitPassword
29+
)
30+
store.addCredentials(domain, usernameAndPassword)
31+
32+
// Add annoymouse access for git webhooks to trigger builds
33+
def strategy = new GlobalMatrixAuthorizationStrategy()
34+
// Setting Anonymous Permissions
35+
strategy.add(hudson.model.Item.BUILD,'anonymous')
36+
strategy.add(hudson.model.Item.CANCEL,'anonymous')
37+
def instance = Jenkins.getInstance()
38+
instance.setAuthorizationStrategy(strategy)
39+
instance.save()
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#!/usr/bin/env groovy
2+
import jenkins.model.*
3+
import com.smartcodeltd.jenkinsci.plugins.buildmonitor.BuildMonitorView
4+
import groovy.json.JsonSlurper
5+
import hudson.tools.InstallSourceProperty
6+
7+
import java.util.logging.Level
8+
import java.util.logging.Logger
9+
import org.csanchez.jenkins.plugins.kubernetes.KubernetesCloud
10+
import jenkins.model.JenkinsLocationConfiguration
11+
12+
final def LOG = Logger.getLogger("LABS")
13+
14+
LOG.log(Level.INFO, 'running configure-jenkins.groovy' )
15+
16+
try {
17+
// delete default OpenShift job
18+
Jenkins.instance.items.findAll {
19+
job -> job.name == 'OpenShift Sample'
20+
}.each {
21+
job -> job.delete()
22+
}
23+
} catch (NullPointerException npe) {
24+
LOG.log(Level.INFO, 'Failed to delete OpenShift Sample job')
25+
}
26+
// create a default build monitor view that includes all jobs
27+
// https://wiki.jenkins-ci.org/display/JENKINS/Build+Monitor+Plugin
28+
if ( Jenkins.instance.views.findAll{ view -> view instanceof com.smartcodeltd.jenkinsci.plugins.buildmonitor.BuildMonitorView }.size == 0){
29+
view = new BuildMonitorView('Build Monitor','Build Monitor')
30+
view.setIncludeRegex('.*')
31+
Jenkins.instance.addView(view)
32+
}
33+
34+
35+
36+
// support custom CSS for htmlreports
37+
// https://stackoverflow.com/questions/35783964/jenkins-html-publisher-plugin-no-css-is-displayed-when-report-is-viewed-in-j
38+
System.setProperty("hudson.model.DirectoryBrowserSupport.CSP", "")
39+
40+
// This is a helper to delete views in the Jenkins script console if needed
41+
// Jenkins.instance.views.findAll{ view -> view instanceof com.smartcodeltd.jenkinsci.plugins.buildmonitor.BuildMonitorView }.each{ view -> Jenkins.instance.deleteView( view ) }
42+
43+
println("WORKAROUND FOR BUILD_URL ISSUE, see: https://issues.jenkins-ci.org/browse/JENKINS-28466")
44+
45+
def hostname = System.getenv('HOSTNAME')
46+
println "hostname> $hostname"
47+
48+
def sout = new StringBuilder(), serr = new StringBuilder()
49+
def proc = "oc get pod ${hostname} -o jsonpath={.metadata.labels.name}".execute()
50+
proc.consumeProcessOutput(sout, serr)
51+
proc.waitForOrKill(3000)
52+
println "out> $sout err> $serr"
53+
54+
def sout2 = new StringBuilder(), serr2 = new StringBuilder()
55+
proc = "oc get route ${sout} -o jsonpath={.spec.host}".execute()
56+
proc.consumeProcessOutput(sout2, serr2)
57+
proc.waitForOrKill(3000)
58+
println "out> $sout2 err> $serr2"
59+
60+
def jlc = jenkins.model.JenkinsLocationConfiguration.get()
61+
jlc.setUrl("https://" + sout2.toString().trim())
62+
63+
println("Configuring container cap for k8s, so pipelines won't hang when booting up slaves")
64+
65+
try{
66+
def kc = Jenkins.instance.clouds.get(0)
67+
68+
println "cloud found: ${Jenkins.instance.clouds}"
69+
70+
kc.setContainerCapStr("100")
71+
}
72+
finally {
73+
//if we don't null kc, jenkins will try to serialise k8s objects and that will fail, so we won't see actual error
74+
kc = null
75+
}
76+
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import jenkins.model.Jenkins
2+
import jenkins.plugins.git.GitSCMSource;
3+
import org.jenkinsci.plugins.workflow.libs.SCMSourceRetriever;
4+
import org.jenkinsci.plugins.workflow.libs.LibraryConfiguration;
5+
6+
import java.util.logging.Level
7+
import java.util.logging.Logger
8+
9+
final def LOG = Logger.getLogger("LABS")
10+
11+
def gitRepo = System.getenv('SHARED_LIB_REPO')
12+
13+
if(gitRepo?.trim()) {
14+
15+
LOG.log(Level.INFO, 'Configuring shared library (implicit)...' )
16+
17+
def sharedLibrary = Jenkins.getInstance().getDescriptor("org.jenkinsci.plugins.workflow.libs.GlobalLibraries")
18+
19+
def libraryName = System.getenv('SHARED_LIB_NAME') ?: "labs-shared-library"
20+
def gitRef = System.getenv('SHARED_LIB_REF') ?: "master"
21+
def secretId = System.getenv('SHARED_LIB_SECRET') ?: ""
22+
23+
//append namespace to secret as it appears in Jenkins
24+
if(secretId) {
25+
secretId = System.getenv('OPENSHIFT_BUILD_NAMESPACE') + "-" + secretId
26+
}
27+
28+
GitSCMSource source = new GitSCMSource( libraryName, gitRepo, secretId, "*", "", false);
29+
SCMSourceRetriever sourceRetriever = new SCMSourceRetriever(source);
30+
31+
LibraryConfiguration pipeline = new LibraryConfiguration(libraryName, sourceRetriever)
32+
pipeline.setDefaultVersion(gitRef)
33+
pipeline.setImplicit(true)
34+
sharedLibrary.get().setLibraries([pipeline])
35+
36+
sharedLibrary.save()
37+
38+
LOG.log(Level.INFO, 'Configured shared library' )
39+
40+
} else {
41+
LOG.log(Level.INFO, 'Skipping shared library configuration')
42+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import jenkins.model.Jenkins
2+
import net.sf.json.JSONObject
3+
4+
import java.util.logging.Level
5+
import java.util.logging.Logger
6+
7+
def slackBaseUrl = System.getenv('SLACK_BASE_URL')
8+
final def LOG = Logger.getLogger("LABS")
9+
10+
if(slackBaseUrl != null) {
11+
12+
LOG.log(Level.INFO, 'Configuring slack...' )
13+
14+
def slackToken = System.getenv('SLACK_TOKEN')
15+
def slackRoom = System.getenv('SLACK_ROOM')
16+
def slackSendAs = ''
17+
def slackTeamDomain = ''
18+
def slackTokenCredentialId = System.getenv('SLACK_TOKEN_CREDENTIAL_ID')
19+
20+
if(slackTokenCredentialId == null) {
21+
slackTokenCredentialId = ''
22+
}
23+
24+
JSONObject formData = ['slack': ['tokenCredentialId': slackTokenCredentialId]] as JSONObject
25+
26+
def slack = Jenkins.instance.getExtensionList(
27+
jenkins.plugins.slack.SlackNotifier.DescriptorImpl.class
28+
)[0]
29+
def params = [
30+
slackBaseUrl: slackBaseUrl,
31+
slackTeamDomain: slackTeamDomain,
32+
slackToken: slackToken,
33+
slackRoom: slackRoom,
34+
slackSendAs: slackSendAs
35+
]
36+
def req = [
37+
getParameter: { name -> params[name] }
38+
] as org.kohsuke.stapler.StaplerRequest
39+
slack.configure(req, formData)
40+
41+
LOG.log(Level.INFO, 'Configured slack' )
42+
43+
slack.save()
44+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#!/usr/bin/env groovy
2+
import jenkins.model.*
3+
import groovy.json.JsonSlurper
4+
import hudson.plugins.sonar.SonarInstallation
5+
import hudson.plugins.sonar.SonarRunnerInstallation
6+
import hudson.plugins.sonar.SonarRunnerInstaller
7+
import hudson.plugins.sonar.model.TriggersConfig
8+
import hudson.tools.InstallSourceProperty
9+
10+
import java.util.logging.Level
11+
import java.util.logging.Logger
12+
13+
final def LOG = Logger.getLogger("LABS")
14+
15+
def disableSonar = System.getenv("DISABLE_SONAR");
16+
if(disableSonar != null && disableSonar.toUpperCase() == "TRUE") {
17+
LOG.log(Level.INFO, 'Skipping SonarQube configuration')
18+
return
19+
}
20+
21+
LOG.log(Level.INFO, 'Configuring SonarQube')
22+
def sonarConfig = Jenkins.instance.getDescriptor('hudson.plugins.sonar.SonarGlobalConfiguration')
23+
24+
def sonarHost = System.getenv("SONARQUBE_URL");
25+
if(sonarHost == null) {
26+
//default
27+
sonarHost = "http://sonarqube:9000"
28+
}
29+
30+
def tokenName = 'Jenkins'
31+
32+
// Make a POST request to delete any existing admin tokens named "Jenkins"
33+
LOG.log(Level.INFO, 'Delete existing SonarQube Jenkins token')
34+
def revokeToken = new URL("${sonarHost}/api/user_tokens/revoke").openConnection()
35+
def message = "name=Jenkins&login=admin"
36+
revokeToken.setRequestMethod("POST")
37+
revokeToken.setDoOutput(true)
38+
revokeToken.setRequestProperty("Accept", "application/json")
39+
def authString = "admin:admin".bytes.encodeBase64().toString()
40+
revokeToken.setRequestProperty("Authorization", "Basic ${authString}")
41+
revokeToken.getOutputStream().write(message.getBytes("UTF-8"))
42+
def rc = revokeToken.getResponseCode()
43+
44+
// Create a new admin token named "Jenkins" and capture the value
45+
LOG.log(Level.INFO, 'Generate new auth token for SonarQube/Jenkins integration')
46+
def generateToken = new URL("${sonarHost}/api/user_tokens/generate").openConnection()
47+
message = "name=${tokenName}&login=admin"
48+
generateToken.setRequestMethod("POST")
49+
generateToken.setDoOutput(true)
50+
generateToken.setRequestProperty("Content-Type", "application/x-www-form-urlencoded")
51+
generateToken.setRequestProperty("Authorization", "Basic ${authString}")
52+
generateToken.getOutputStream().write(message.getBytes("UTF-8"))
53+
rc = generateToken.getResponseCode()
54+
55+
def token = null
56+
57+
if (rc == 200) {
58+
LOG.log(Level.INFO, 'Successfully generated SonarQube auth token')
59+
def jsonBody = generateToken.getInputStream().getText()
60+
def jsonParser = new JsonSlurper()
61+
def data = jsonParser.parseText(jsonBody)
62+
token = data.token
63+
64+
// Add the SonarQube server config to Jenkins
65+
SonarInstallation sonarInst = new SonarInstallation(
66+
"sonar", sonarHost, token, "", "", new TriggersConfig(), "")
67+
sonarConfig.setInstallations(sonarInst)
68+
sonarConfig.setBuildWrapperEnabled(true)
69+
sonarConfig.save()
70+
71+
// Sonar Runner
72+
// Source: http://pghalliday.com/jenkins/groovy/sonar/chef/configuration/management/2014/09/21/some-useful-jenkins-groovy-scripts.html
73+
def inst = Jenkins.getInstance()
74+
75+
def sonarRunner = inst.getDescriptor("hudson.plugins.sonar.SonarRunnerInstallation")
76+
77+
def installer = new SonarRunnerInstaller("3.0.3.778")
78+
def prop = new InstallSourceProperty([installer])
79+
def sinst = new SonarRunnerInstallation("sonar-scanner-tool", "", [prop])
80+
sonarRunner.setInstallations(sinst)
81+
82+
sonarRunner.save()
83+
84+
LOG.log(Level.INFO, 'SonarQube configuration complete')
85+
} else {
86+
LOG.log(Level.INFO, "Request failed: ${rc}")
87+
LOG.log(Level.INFO, generateToken.getErrorStream().getText())
88+
}

configuration/scriptApproval.xml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?xml version='1.0' encoding='UTF-8'?>
2+
<scriptApproval plugin="[email protected]">
3+
<approvedScriptHashes/>
4+
<approvedSignatures>
5+
<string>staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods drop java.lang.CharSequence int</string>
6+
<string>staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods reverse java.lang.String</string>
7+
<string>staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods minus java.lang.String</string>
8+
<string>staticMethod org.codehaus.groovy.runtime.EncodingGroovyMethods decodeBase64 java.lang.String</string>
9+
<string>method hudson.plugins.git.GitSCM getUserRemoteConfigs</string>
10+
<string>new java.lang.String byte[]</string>
11+
<string>staticMethod java.lang.System getenv java.lang.String</string>
12+
<string>staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods execute java.lang.String</string>
13+
<string>staticMethod org.codehaus.groovy.runtime.ProcessGroovyMethods getText java.lang.Process</string>
14+
</approvedSignatures>
15+
<aclApprovedSignatures/>
16+
<approvedClasspathEntries/>
17+
<pendingScripts/>
18+
<pendingSignatures/>
19+
<pendingClasspathEntries/>
20+
</scriptApproval>

helpers/README.MD

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Introduction
2+
3+
A semi automated hack to get updated plugin list. Here's how the helper works:
4+
5+
0. Make sure you have groovy installed, which is available as a package on most linux distros, or via [the download site](http://groovy-lang.org/download.html).
6+
1. Login in the jenkins you want to replicate.
7+
2. Post fix the jenkins url with `/pluginManager/api/json?depth=1&tree=plugins[shortName,version]` in your browser.
8+
3. Copy the output to a new file called `out.json` in the `helpers` dir.
9+
4. Run `./writePluginFileFromExistingJenkins.groovy` to construct a `plugins.txt` file to be used with the [OpenShift Jenkins S2I image](https://github.com/openshift/jenkins/blob/master/README.md#installing-using-s2i-build)
10+
5. Delete `out.json`
11+
6. Check in the new `plugins.txt`

0 commit comments

Comments
 (0)