├── Jenkinsfile
├── .mvn
├── maven.config
└── extensions.xml
├── images
└── configuration.png
├── src
├── main
│ ├── resources
│ │ ├── index.jelly
│ │ └── io
│ │ │ └── alauda
│ │ │ └── jenkins
│ │ │ └── devops
│ │ │ └── support
│ │ │ ├── KubernetesClusterConfiguration
│ │ │ └── config.jelly
│ │ │ └── KubernetesCluster
│ │ │ └── config.jelly
│ └── java
│ │ └── io
│ │ └── alauda
│ │ └── jenkins
│ │ └── devops
│ │ └── support
│ │ ├── KubernetesClusterConfigurationListener.java
│ │ ├── exception
│ │ └── KubernetesClientException.java
│ │ ├── utils
│ │ ├── CredentialsUtils.java
│ │ └── SyncPluginConfigurationCompatiblilityMigrater.java
│ │ ├── KubernetesClusterConfiguration.java
│ │ ├── client
│ │ └── Clients.java
│ │ └── KubernetesCluster.java
└── test
│ └── java
│ └── io
│ └── alauda
│ └── jenkins
│ └── devops
│ └── support
│ └── KubernetesClusterConfigurationTest.java
├── scripts
├── run.sh
├── restart.sh
└── upload.sh
├── README.md
├── sonar-project.properties
├── LICENSE
├── .github
└── settings.yml
├── .gitignore
├── Alauda.Jenkinsfile
└── pom.xml
/Jenkinsfile:
--------------------------------------------------------------------------------
1 | buildPlugin()
--------------------------------------------------------------------------------
/.mvn/maven.config:
--------------------------------------------------------------------------------
1 | -Pconsume-incrementals
2 | -Pmight-produce-incrementals
3 |
--------------------------------------------------------------------------------
/images/configuration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alauda-kubernetes-support-plugin/master/images/configuration.png
--------------------------------------------------------------------------------
/src/main/resources/index.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 | This plugin allows users to config kubernetes and share configuration to other plugins.
4 |
5 |
--------------------------------------------------------------------------------
/src/main/resources/io/alauda/jenkins/devops/support/KubernetesClusterConfiguration/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.mvn/extensions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | io.jenkins.tools.incrementals
4 | git-changelist-maven-extension
5 | 1.0-beta-7
6 |
7 |
8 |
--------------------------------------------------------------------------------
/scripts/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | TOKEN_ADMIN="c6fc6a59033d1144fec5a565cb2b6796"
6 | ADDRESS="http://localhost:30251"
7 | export JENKINS_CONTAINER=$(docker ps | grep jenkins | head -n1 | awk '{print $1;}')
8 | echo $JENKINS_CONTAINER
9 | if [ "$JENKINS_CONTAINER" == "" ]; then
10 | echo "Jenkins container not found"
11 | exit 1;
12 | fi
13 | mvn clean install -DskipTests
14 | docker cp target/alauda-devops-sync.hpi $JENKINS_CONTAINER:/var/jenkins_home/plugins
15 | ./restart.sh
16 |
--------------------------------------------------------------------------------
/scripts/restart.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | TOKEN_ADMIN="c6fc6a59033d1144fec5a565cb2b6796"
6 | ADDRESS="http://localhost:30251"
7 | NAMESPACE=default-1
8 | DEPLOY=jenkins
9 |
10 | echo "Restarting jenkins..."
11 | curl -v -XPOST $ADDRESS/safeRestart -u 'admin:'$TOKEN_ADMIN
12 | kubectl logs -f --tail=50 -n $NAMESPACE deploy/$DEPLOY
13 | echo "===================================================="
14 | echo "Jenkins restarted... tailling logs again..."
15 | echo "===================================================="
16 | sleep 2
17 | kubectl logs -f --tail=100 -n $NAMESPACE deploy/$DEPLOY
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Alauda Kubernetes Support Plugin
2 |
3 | This plugin allows user to config kubernetes cluster and provide correspondent client for other plugins to use.
4 |
5 | 
6 |
7 |
8 | * Kubernetes URL - the address of Kubernetes Cluster, if left it to blank, it will use local cluster address.
9 | * Credentials - the token to connect to cluster, the Credentials type is Secret Text.
10 | * Disable https certificate check - whether to skip tls verify
11 | * Server Certificate Authority - certificate used to create secure connections to cluster
12 |
--------------------------------------------------------------------------------
/sonar-project.properties:
--------------------------------------------------------------------------------
1 | # must be unique in a given SonarQube instance
2 | sonar.projectKey=alauda-devops-support
3 | # this is the name and version displayed in the SonarQube UI. Was mandatory prior to SonarQube 6.1.
4 | sonar.projectName=alauda-devops-support-plugin
5 |
6 | # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
7 | # This property is optional if sonar.modules is set.
8 | sonar.sources=src/main/java
9 |
10 | # Encoding of the source code. Default is default system encoding
11 | sonar.sourceEncoding=UTF-8
12 |
13 | sonar.java.source=1.8
14 |
15 | sonar.java.binaries=./target/classes
16 |
--------------------------------------------------------------------------------
/src/main/java/io/alauda/jenkins/devops/support/KubernetesClusterConfigurationListener.java:
--------------------------------------------------------------------------------
1 | package io.alauda.jenkins.devops.support;
2 |
3 | import hudson.ExtensionList;
4 | import hudson.ExtensionPoint;
5 | import io.kubernetes.client.ApiClient;
6 |
7 | public interface KubernetesClusterConfigurationListener extends ExtensionPoint {
8 | void onConfigChange(KubernetesCluster cluster, ApiClient client);
9 |
10 | void onConfigError(KubernetesCluster cluster, Throwable reason);
11 |
12 | static ExtensionList all() {
13 | return ExtensionList.lookup(KubernetesClusterConfigurationListener.class);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/io/alauda/jenkins/devops/support/exception/KubernetesClientException.java:
--------------------------------------------------------------------------------
1 | package io.alauda.jenkins.devops.support.exception;
2 |
3 | public class KubernetesClientException extends Exception {
4 | public KubernetesClientException() {
5 | }
6 |
7 | public KubernetesClientException(String message) {
8 | super(message);
9 | }
10 |
11 | public KubernetesClientException(String message, Throwable cause) {
12 | super(message, cause);
13 | }
14 |
15 | public KubernetesClientException(Throwable cause) {
16 | super(cause);
17 | }
18 |
19 | public KubernetesClientException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
20 | super(message, cause, enableSuppression, writableStackTrace);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Alauda.io
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.github/settings.yml:
--------------------------------------------------------------------------------
1 | repository:
2 | name: alauda-kubernetes-support-plugin
3 | description: Alauda Kubernetes support plugin
4 | homepage: http://www.alauda.cn/
5 | private: false
6 | has_issues: true
7 | has_wiki: false
8 | has_downloads: false
9 | default_branch: master
10 | allow_squash_merge: true
11 | allow_merge_commit: true
12 | allow_rebase_merge: true
13 | labels:
14 | - name: newbie
15 | color: abe7f4
16 | description: 新手上路
17 | - name: bug
18 | color: d73a4a
19 | description: Something isn't working
20 | - name: enhancement
21 | color: a2eeef
22 | description: New feature or request
23 | - name: help wanted
24 | color: 008672
25 | description: Extra attention is needed
26 | branches:
27 | - name: master
28 | protection:
29 | required_pull_request_reviews:
30 | required_approving_review_count: 2
31 | dismiss_stale_reviews: true
32 | require_code_owner_reviews: false
33 | dismissal_restrictions:
34 | users: []
35 | teams: []
36 | required_status_checks:
37 | strict: true
38 | contexts: []
39 | enforce_admins: false
40 | restrictions:
41 | users: []
42 | teams: []
43 |
--------------------------------------------------------------------------------
/src/main/resources/io/alauda/jenkins/devops/support/KubernetesCluster/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
26 |
--------------------------------------------------------------------------------
/src/main/java/io/alauda/jenkins/devops/support/utils/CredentialsUtils.java:
--------------------------------------------------------------------------------
1 | package io.alauda.jenkins.devops.support.utils;
2 |
3 | import com.cloudbees.plugins.credentials.CredentialsMatchers;
4 | import com.cloudbees.plugins.credentials.CredentialsProvider;
5 | import hudson.security.ACL;
6 | import jenkins.model.Jenkins;
7 | import org.apache.commons.lang.StringUtils;
8 | import org.jenkinsci.plugins.plaincredentials.StringCredentials;
9 |
10 | import java.security.GeneralSecurityException;
11 | import java.util.Collections;
12 |
13 | public class CredentialsUtils {
14 | public static String getToken(String credentialId) throws GeneralSecurityException {
15 | if (StringUtils.isEmpty(credentialId)) {
16 | return "";
17 | }
18 |
19 | StringCredentials secretCredentials =
20 | CredentialsMatchers.firstOrNull(
21 | CredentialsProvider.lookupCredentials(StringCredentials.class, Jenkins.getInstance(), ACL.SYSTEM, Collections.emptyList()),
22 | CredentialsMatchers.withId(credentialId));
23 |
24 | if (secretCredentials == null) {
25 | throw new GeneralSecurityException(String.format("Credential with id %s not found", credentialId));
26 | }
27 |
28 | return secretCredentials.getSecret().getPlainText();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/scripts/upload.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # current script needs env.sh to setup some environment variables, see also example file below:
3 | # export JENKINS_USER=admin
4 | # export JENKINS_TOKEN=11aa342e9a392f64cc7c56548e17fe6d09
5 | # export JENKINS_URL=http://jenkins.com
6 | #
7 | # export REMOTE_USER=193151e7-48e9-402c-a608-a10301a29a72
8 | # export REMOTE_TOKEN=274e8a79536e8a34f596a5a5d17d3e46
9 | # export REMOTE_URL=http://jenkins.cn
10 |
11 | if [ "$JENKINS_USER" == "" ]; then
12 | export JENKINS_USER=admin
13 | fi
14 |
15 | if [ ! -f "$(dirname "${BASH_SOURCE[0]}")/env.sh" ]; then
16 | echo 'we need the env.sh to setup vars'
17 | exit -1
18 | fi
19 |
20 | source $(dirname "${BASH_SOURCE[0]}")/env.sh
21 |
22 | issuer=$(curl -k -u $JENKINS_USER:$JENKINS_TOKEN $JENKINS_URL/crumbIssuer/api/json -s -o /dev/null -w %{http_code})
23 | if [ "$issuer" == "200" ]; then
24 | export issuer=$(curl -k -u $JENKINS_USER:$JENKINS_TOKEN $JENKINS_URL/crumbIssuer/api/json -s)
25 | issuer=$(python -c "import json;import os;issuer=os.getenv('issuer');issuer=json.loads(issuer);print issuer['crumb']")
26 | else
27 | issuer=""
28 | fi
29 |
30 | export target_file=$(dirname "${BASH_SOURCE[0]}")"/../target/alauda-kubernetes-support.hpi"
31 | echo "target file is $target_file"
32 |
33 | # support fetch plugin from the remote server
34 | if [ "$#" == "1" ]; then
35 | if [[ "$1" =~ ^http.* ]]; then
36 | echo "going to download plugin from remote: $1"
37 |
38 | remote_issuer=$(curl -k -u $REMOTE_USER:$REMOTE_TOKEN $REMOTE_URL/crumbIssuer/api/json -s -o /dev/null -w %{http_code})
39 | if [ "$remote_issuer" == "200" ]; then
40 | export remote_issuer=$(curl -k -u $REMOTE_USER:$REMOTE_TOKEN $REMOTE_URL/crumbIssuer/api/json -s)
41 | remote_issuer=$(python -c "import json;import os;issuer=os.getenv('remote_issuer');issuer=json.loads(issuer);print issuer['crumb']")
42 | else
43 | remote_issuer=""
44 | fi
45 | curl -k -u $REMOTE_USER:$REMOTE_TOKEN $1 --header "Jenkins-Crumb: $remote_issuer" -o $target_file
46 | else
47 | echo "not a correct url: $1"
48 | fi
49 | fi
50 |
51 | curl -k -u $JENKINS_USER:$JENKINS_TOKEN $JENKINS_URL/pluginManager/uploadPlugin -F "name=@$target_file" --header "Jenkins-Crumb: $issuer"
52 | curl -k -u $JENKINS_USER:$JENKINS_TOKEN $JENKINS_URL/restart -X POST --header "Jenkins-Crumb: $issuer"
53 |
--------------------------------------------------------------------------------
/src/test/java/io/alauda/jenkins/devops/support/KubernetesClusterConfigurationTest.java:
--------------------------------------------------------------------------------
1 | package io.alauda.jenkins.devops.support;
2 |
3 | import com.gargoylesoftware.htmlunit.html.HtmlCheckBoxInput;
4 | import com.gargoylesoftware.htmlunit.html.HtmlForm;
5 | import com.gargoylesoftware.htmlunit.html.HtmlTextInput;
6 | import org.junit.Test;
7 | import static org.junit.Assert.*;
8 | import org.junit.Rule;
9 | import org.jvnet.hudson.test.RestartableJenkinsRule;
10 |
11 | public class KubernetesClusterConfigurationTest {
12 |
13 | @Rule
14 | public RestartableJenkinsRule rr = new RestartableJenkinsRule();
15 |
16 | @Test
17 | public void uiAndStorage() {
18 | final String masterUrl = "http://localhost/master";
19 | final boolean defaultCluster = true;
20 | final boolean managerCluster = false;
21 |
22 | rr.then(r -> {
23 | assertNull("not set initially", KubernetesClusterConfiguration.get().getCluster());
24 | HtmlForm config = r.createWebClient().goTo("configure").getFormByName("config");
25 | HtmlTextInput textbox = config.getInputByName("_.masterUrl");
26 | textbox.setText(masterUrl);
27 | HtmlCheckBoxInput checkBoxInput = config.getInputByName("_.skipTlsVerify");
28 | checkBoxInput.setChecked(false);
29 | config.getInputByName("_.defaultCluster").setChecked(defaultCluster);
30 | config.getInputByName("_.managerCluster").setChecked(managerCluster);
31 | r.submit(config);
32 |
33 | // assert config result
34 | assertClusterConfig(masterUrl, defaultCluster, managerCluster);
35 | });
36 | rr.then(r -> {
37 | assertClusterConfig(masterUrl, defaultCluster, managerCluster);
38 | });
39 | }
40 |
41 | private void assertClusterConfig(final String masterUrl, final boolean defaultCluster, final boolean managerCluster) {
42 | KubernetesClusterConfiguration clusterConfig = KubernetesClusterConfiguration.get();
43 | assertEquals("global config page let us edit it", masterUrl,
44 | clusterConfig.getCluster().getMasterUrl());
45 | assertEquals("DefaultCluster setting is not working", defaultCluster,
46 | clusterConfig.getCluster().isDefaultCluster());
47 | assertEquals("ManagerCluster setting is not working", managerCluster,
48 | clusterConfig.getCluster().isManagerCluster());
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Example user template template
3 | ### Example user template
4 |
5 | # IntelliJ project files
6 | .idea
7 | *.iml
8 | out
9 | gen### JetBrains template
10 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
11 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
12 |
13 | # User-specific stuff
14 | .idea/**/tasks.xml
15 | .idea/**/dictionaries
16 | .idea/**/shelf
17 |
18 | # Sensitive or high-churn files
19 | .idea/**/dataSources/
20 | .idea/**/dataSources.ids
21 | .idea/**/dataSources.local.xml
22 | .idea/**/sqlDataSources.xml
23 | .idea/**/dynamic.xml
24 | .idea/**/uiDesigner.xml
25 | .idea/**/dbnavigator.xml
26 |
27 | # Gradle
28 | .idea/**/gradle.xml
29 | # CMake
30 | cmake-build-debug/
31 | cmake-build-release/
32 |
33 | # Mongo Explorer plugin
34 | .idea/**/mongoSettings.xml
35 |
36 | # File-based project format
37 | *.iws
38 |
39 | # IntelliJ
40 | out/
41 |
42 | # mpeltonen/sbt-idea plugin
43 | .idea_modules/
44 |
45 | # JIRA plugin
46 | atlassian-ide-plugin.xml
47 |
48 | # Cursive Clojure plugin
49 | .idea/replstate.xml
50 |
51 | # Crashlytics plugin (for Android Studio and IntelliJ)
52 | com_crashlytics_export_strings.xml
53 | crashlytics.properties
54 | crashlytics-build.properties
55 | fabric.properties
56 |
57 | # Editor-based Rest Client
58 | .idea/httpRequests
59 | ### Java template
60 | # Compiled class file
61 | *.class
62 |
63 | # Log file
64 | *.log
65 |
66 | # BlueJ files
67 | *.ctxt
68 |
69 | # Mobile Tools for Java (J2ME)
70 | .mtj.tmp/
71 |
72 | # Package Files #
73 | *.jar
74 | *.war
75 | *.nar
76 | *.ear
77 | *.zip
78 | *.tar.gz
79 | *.rar
80 |
81 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
82 | hs_err_pid*
83 | ### macOS template
84 | # General
85 | .DS_Store
86 | .AppleDouble
87 | .LSOverride
88 |
89 | # Icon must end with two \r
90 | Icon
91 |
92 | # Thumbnails
93 | ._*
94 |
95 | # Files that might appear in the root of a volume
96 | .DocumentRevisions-V100
97 | .fseventsd
98 | .Spotlight-V100
99 | .TemporaryItems
100 | .Trashes
101 | .VolumeIcon.icns
102 | .com.apple.timemachine.donotpresent
103 |
104 | # Directories potentially created on remote AFP share
105 | .AppleDB
106 | .AppleDesktop
107 | Network Trash Folder
108 | Temporary Items
109 | .apdisk
110 |
111 | work/
112 | target/
113 | scripts/env.sh
114 |
115 |
--------------------------------------------------------------------------------
/src/main/java/io/alauda/jenkins/devops/support/KubernetesClusterConfiguration.java:
--------------------------------------------------------------------------------
1 | package io.alauda.jenkins.devops.support;
2 |
3 | import hudson.Extension;
4 | import io.alauda.jenkins.devops.support.client.Clients;
5 | import io.alauda.jenkins.devops.support.exception.KubernetesClientException;
6 | import io.alauda.jenkins.devops.support.utils.SyncPluginConfigurationCompatiblilityMigrater;
7 | import io.kubernetes.client.ApiClient;
8 | import io.kubernetes.client.Configuration;
9 | import jenkins.model.GlobalConfiguration;
10 | import org.kohsuke.stapler.DataBoundSetter;
11 |
12 | import java.util.LinkedList;
13 | import java.util.List;
14 | import java.util.concurrent.TimeUnit;
15 | import java.util.logging.Level;
16 | import java.util.logging.Logger;
17 |
18 | @Extension(ordinal = 203)
19 | public class KubernetesClusterConfiguration extends GlobalConfiguration {
20 | private static final Logger logger = Logger.getLogger(KubernetesClusterConfiguration.class.getName());
21 |
22 | // We might config multiple servers in the future, so we use list to store them
23 | private List k8sClusters = new LinkedList<>();
24 |
25 | public static KubernetesClusterConfiguration get() {
26 | return GlobalConfiguration.all().get(KubernetesClusterConfiguration.class);
27 | }
28 |
29 |
30 | public KubernetesClusterConfiguration() {
31 | // When Jenkins is restarted, load any saved configuration from disk.
32 | load();
33 | KubernetesCluster clusterMigrateFromSync = SyncPluginConfigurationCompatiblilityMigrater.migrateConfigurationFromSyncPlugin();
34 | if (clusterMigrateFromSync != null) {
35 | clusterMigrateFromSync.setDefaultCluster(true);
36 | clusterMigrateFromSync.setManagerCluster(true);
37 | setCluster(clusterMigrateFromSync);
38 | return;
39 | }
40 |
41 |
42 | if (k8sClusters.size() == 0) {
43 | KubernetesCluster cluster = new KubernetesCluster();
44 | // WARN once we support multi-cluster configuration, you should change this behavior
45 | cluster.setDefaultCluster(true);
46 | cluster.setManagerCluster(true);
47 | setCluster(cluster);
48 | } else {
49 | triggerEvents(k8sClusters.get(0));
50 | }
51 |
52 | }
53 |
54 | public KubernetesCluster getCluster() {
55 | if (k8sClusters == null || k8sClusters.size() == 0) {
56 | return null;
57 | }
58 |
59 | return k8sClusters.get(0);
60 | }
61 |
62 |
63 | /**
64 | * Together with {@link #getCluster()}, binds to entry in {@code config.jelly}.
65 | *
66 | * @param cluster the new value of this field
67 | */
68 | @DataBoundSetter
69 | public void setCluster(KubernetesCluster cluster) {
70 | if (k8sClusters == null) {
71 | k8sClusters = new LinkedList<>();
72 | }
73 |
74 | KubernetesCluster currentCluster = getCluster();
75 | if (currentCluster != null && currentCluster.equals(cluster)) {
76 | return;
77 | }
78 |
79 | k8sClusters.clear();
80 | k8sClusters.add(cluster);
81 | save();
82 |
83 | triggerEvents(cluster);
84 | }
85 |
86 |
87 | private void triggerEvents(KubernetesCluster cluster) {
88 | try {
89 | ApiClient client = Clients.createClientFromCluster(cluster);
90 | client.getHttpClient().setReadTimeout(0, TimeUnit.SECONDS);
91 | // If we have more clusters to config in the future, we may need to remove this.
92 | Configuration.setDefaultApiClient(client);
93 |
94 | new Thread(() -> triggerConfigChangeEvent(cluster, client)).start();
95 | } catch (KubernetesClientException e) {
96 | e.printStackTrace();
97 | logger.log(Level.SEVERE, String.format("Unable to create client from cluster %s, reason %s", cluster.getMasterUrl(), e.getMessage()));
98 | new Thread(() -> triggerConfigErrorEvent(cluster, e)).start();
99 | }
100 | }
101 |
102 |
103 | private void triggerConfigChangeEvent(KubernetesCluster cluster, ApiClient client) {
104 | KubernetesClusterConfigurationListener
105 | .all()
106 | .forEach(listener ->
107 | new Thread(() -> listener.onConfigChange(cluster, client)).start());
108 |
109 | }
110 |
111 | private void triggerConfigErrorEvent(KubernetesCluster cluster, Throwable reason) {
112 | KubernetesClusterConfigurationListener.all()
113 | .forEach(listener ->
114 | listener.onConfigError(cluster, reason));
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/Alauda.Jenkinsfile:
--------------------------------------------------------------------------------
1 | // https://jenkins.io/doc/book/pipeline/syntax/
2 | // Multi-branch discovery pattern: PR-.*
3 | @Library('alauda-cicd') _
4 |
5 | // global variables for pipeline
6 | def GIT_BRANCH
7 | def GIT_COMMIT
8 | def FOLDER = "."
9 | def DEBUG = false
10 | def deployment
11 | def RELEASE_VERSION
12 | def RELEASE_BUILD
13 | pipeline {
14 | // 运行node条件
15 | // 为了扩容jenkins的功能一般情况会分开一些功能到不同的node上面
16 | // 这样每个node作用比较清晰,并可以并行处理更多的任务量
17 | agent { label 'golang && java' }
18 |
19 | // (optional) 流水线全局设置
20 | options {
21 | // 保留多少流水线记录(建议不放在jenkinsfile里面)
22 | buildDiscarder(logRotator(numToKeepStr: '10'))
23 |
24 | // 不允许并行执行
25 | disableConcurrentBuilds()
26 | }
27 |
28 | //(optional) 环境变量
29 | environment {
30 | // for building an scanning
31 | REPOSITORY = "alauda-kubernetes-support-plugin"
32 | OWNER = "alauda"
33 | // sonar feedback user
34 | // needs to change together with the credentialsID
35 | SCM_FEEDBACK_ACCOUNT = "alaudabot"
36 | SONARQUBE_SCM_CREDENTIALS = "alaudabot"
37 | DEPLOYMENT = "alauda-kubernetes-support-plugin"
38 | DINGDING_BOT = "devops-chat-bot"
39 | TAG_CREDENTIALS = "alaudabot-github"
40 | }
41 | // stages
42 | stages {
43 | stage('Checkout') {
44 | steps {
45 | script {
46 | // checkout code
47 | def scmVars = checkout scm
48 | // extract git information
49 | env.GIT_COMMIT = scmVars.GIT_COMMIT
50 | env.GIT_BRANCH = scmVars.GIT_BRANCH
51 | GIT_COMMIT = "${scmVars.GIT_COMMIT}"
52 | GIT_BRANCH = "${scmVars.GIT_BRANCH}"
53 | pom = readMavenPom file: 'pom.xml'
54 | //RELEASE_VERSION = pom.properties['revision'] + pom.properties['sha1'] + pom.properties['changelist']
55 | RELEASE_VERSION = pom.version
56 | }
57 | container('golang'){
58 | // installing golang coverage and report tools
59 | sh "go get -u github.com/alauda/gitversion"
60 | script {
61 | if (GIT_BRANCH != "master") {
62 | def branch = GIT_BRANCH.replace("/","-").replace("_","-")
63 | RELEASE_BUILD = "${RELEASE_VERSION}.${branch}.${env.BUILD_NUMBER}"
64 | } else {
65 | sh "gitversion patch ${RELEASE_VERSION} > patch"
66 | RELEASE_BUILD = readFile("patch").trim()
67 | }
68 |
69 | sh '''
70 | echo "commit=$GIT_COMMIT" > src/main/resources/debug.properties
71 | echo "build=$RELEASE_BUILD" >> src/main/resources/debug.properties
72 | echo "version=RELEASE_VERSION" >> src/main/resources/debug.properties
73 | cat src/main/resources/debug.properties
74 | '''
75 | }
76 | }
77 | }
78 | }
79 | stage('Build') {
80 | steps {
81 | script {
82 | container('java'){
83 | sh """
84 | mvn clean install -U findbugs:findbugs -Dmaven.test.skip=true
85 | """
86 | }
87 |
88 | archiveArtifacts 'target/*.hpi'
89 | }
90 | }
91 | }
92 | // stage('Release') {
93 | // when {
94 | // expression {
95 | // GIT_BRANCH == "master"
96 | // }
97 | // }
98 | // steps {
99 | // script {
100 | // container('java'){
101 | // if(RELEASE_VERSION.endsWith('-SNAPSHOT')){
102 | // sh 'mvn clean deploy -U -DskipTests'
103 | // } else {
104 | // }
105 | // }
106 | // }
107 | // }
108 | // }
109 | // sonar scan
110 | stage('Sonar') {
111 | steps {
112 | script {
113 | container('tools') {
114 | deploy.scan(
115 | REPOSITORY,
116 | GIT_BRANCH,
117 | SONARQUBE_SCM_CREDENTIALS,
118 | FOLDER,
119 | DEBUG,
120 | OWNER,
121 | SCM_FEEDBACK_ACCOUNT).start()
122 | }
123 | }
124 | }
125 | }
126 | }
127 |
128 | // (optional)
129 | // happens at the end of the pipeline
130 | post {
131 | // 成功
132 | success {
133 | echo "Horay!"
134 | script {
135 | deploy.notificationSuccess(DEPLOYMENT, DINGDING_BOT, "流水线完成了", RELEASE_BUILD)
136 | }
137 | }
138 | // 失败
139 | failure {
140 | // check the npm log
141 | // fails lets check if it
142 | script {
143 | echo "damn!"
144 | deploy.notificationFailed(DEPLOYMENT, DINGDING_BOT, "流水线失败了", RELEASE_BUILD)
145 | }
146 | }
147 | always { junit allowEmptyResults: true, testResults: '**/target/surefire-reports/**/*.xml' }
148 | }
149 | }
150 |
151 |
--------------------------------------------------------------------------------
/src/main/java/io/alauda/jenkins/devops/support/client/Clients.java:
--------------------------------------------------------------------------------
1 | package io.alauda.jenkins.devops.support.client;
2 |
3 | import io.alauda.jenkins.devops.support.KubernetesCluster;
4 | import io.alauda.jenkins.devops.support.exception.KubernetesClientException;
5 | import io.alauda.jenkins.devops.support.utils.CredentialsUtils;
6 | import io.kubernetes.client.ApiClient;
7 | import io.kubernetes.client.util.Config;
8 | import okio.Buffer;
9 | import org.apache.commons.lang.StringUtils;
10 | import org.jenkinsci.remoting.util.Charsets;
11 |
12 | import javax.annotation.Nonnull;
13 | import java.io.File;
14 | import java.io.FileNotFoundException;
15 | import java.io.IOException;
16 | import java.nio.file.Files;
17 | import java.nio.file.Paths;
18 | import java.security.GeneralSecurityException;
19 | import java.util.logging.Level;
20 | import java.util.logging.Logger;
21 |
22 | public final class Clients {
23 |
24 | private static final Logger logger = Logger.getLogger(Clients.class.getName());
25 |
26 | private Clients() {
27 | }
28 |
29 | /**
30 | * Return a client. If client not exists, it will create a client from the cluster then return it.
31 | *
32 | * @param cluster Kubernetes cluster
33 | * @return Client that can connect to correspondent cluster, null if we cannot create client from cluster.
34 | */
35 | @Nonnull
36 | public static ApiClient createClientFromCluster(@Nonnull KubernetesCluster cluster) throws KubernetesClientException {
37 | ApiClient client;
38 | // if master url is empty, we create client from local cluster
39 | if (StringUtils.isEmpty(cluster.getMasterUrl())) {
40 | try {
41 | client = Config.fromCluster();
42 | return client;
43 | } catch (IOException e) {
44 | logger.log(Level.SEVERE, String.format("Unable to create a client from local cluster, reason %s", e.getMessage()), e);
45 | throw new KubernetesClientException(e);
46 | }
47 | }
48 |
49 | String token = "";
50 | try {
51 | if (StringUtils.isEmpty(cluster.getCredentialsId())) {
52 | token = getTokenFromLocalCluster();
53 | } else {
54 | token = CredentialsUtils.getToken(cluster.getCredentialsId());
55 | }
56 | } catch (GeneralSecurityException | IOException e) {
57 | logger.log(Level.WARNING, String.format("Unable to get token for k8s client, reason %s", e.getMessage()), e);
58 | }
59 | client = Config.fromToken(cluster.getMasterUrl(), token, !cluster.isSkipTlsVerify());
60 |
61 | if (!cluster.isSkipTlsVerify()) {
62 | try {
63 | Buffer buffer = new Buffer();
64 |
65 | if (!StringUtils.isEmpty(cluster.getServerCertificateAuthority())) {
66 | if (new File(cluster.getServerCertificateAuthority()).isFile()) {
67 | buffer.write(Files.readAllBytes(Paths.get(cluster.getServerCertificateAuthority())));
68 | } else {
69 | buffer.writeUtf8(cluster.getServerCertificateAuthority());
70 | }
71 | } else {
72 | buffer.writeUtf8(getCAFromLocalCluster());
73 | }
74 | if (buffer.size() != 0) {
75 | client.setSslCaCert(buffer.inputStream());
76 | }
77 | } catch (IOException e) {
78 | logger.log(Level.WARNING, String.format("Unable to get ca for k8s client, reason %s", e.getMessage()), e);
79 | }
80 | }
81 | return client;
82 | }
83 |
84 | public static ApiClient createClientFromConfig(String masterUrl, String credentialsId, String serverCertificateAuthority, boolean skipTlsVerify) throws KubernetesClientException {
85 | KubernetesCluster cluster = new KubernetesCluster();
86 | cluster.setMasterUrl(masterUrl);
87 | cluster.setCredentialsId(credentialsId);
88 | cluster.setServerCertificateAuthority(serverCertificateAuthority);
89 | cluster.setSkipTlsVerify(skipTlsVerify);
90 |
91 | return createClientFromCluster(cluster);
92 | }
93 |
94 |
95 | @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(
96 | value = "DMI_HARDCODED_ABSOLUTE_FILENAME",
97 | justification = "I know what I'm doing")
98 | @Nonnull
99 | private static String getTokenFromLocalCluster() throws IOException {
100 | if (Files.exists(Paths.get(Config.SERVICEACCOUNT_TOKEN_PATH))) {
101 | return new String(Files.readAllBytes(Paths.get(Config.SERVICEACCOUNT_TOKEN_PATH)), Charsets.UTF_8);
102 | }
103 | throw new FileNotFoundException(String.format("Unable to get token from %s", Config.SERVICEACCOUNT_TOKEN_PATH));
104 | }
105 |
106 | @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(
107 | value = "DMI_HARDCODED_ABSOLUTE_FILENAME",
108 | justification = "I know what I'm doing")
109 | @Nonnull
110 | private static String getCAFromLocalCluster() throws IOException {
111 | if (Files.exists(Paths.get(Config.SERVICEACCOUNT_CA_PATH))) {
112 | return new String(Files.readAllBytes(Paths.get(Config.SERVICEACCOUNT_CA_PATH)), Charsets.UTF_8);
113 | }
114 | throw new FileNotFoundException(String.format("Unable to get CA from %s", Config.SERVICEACCOUNT_CA_PATH));
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/main/java/io/alauda/jenkins/devops/support/utils/SyncPluginConfigurationCompatiblilityMigrater.java:
--------------------------------------------------------------------------------
1 | package io.alauda.jenkins.devops.support.utils;
2 |
3 | import io.alauda.jenkins.devops.support.KubernetesCluster;
4 | import jenkins.model.Jenkins;
5 | import org.apache.commons.io.output.FileWriterWithEncoding;
6 | import org.w3c.dom.Document;
7 | import org.w3c.dom.Element;
8 | import org.w3c.dom.NodeList;
9 | import org.xml.sax.SAXException;
10 |
11 | import javax.xml.XMLConstants;
12 | import javax.xml.parsers.DocumentBuilder;
13 | import javax.xml.parsers.DocumentBuilderFactory;
14 | import javax.xml.parsers.ParserConfigurationException;
15 | import javax.xml.transform.*;
16 | import javax.xml.transform.dom.DOMSource;
17 | import javax.xml.transform.stream.StreamResult;
18 | import java.io.File;
19 | import java.io.IOException;
20 | import java.nio.charset.StandardCharsets;
21 | import java.util.logging.Level;
22 | import java.util.logging.Logger;
23 |
24 | public class SyncPluginConfigurationCompatiblilityMigrater {
25 | private static final Logger logger = Logger.getLogger(SyncPluginConfigurationCompatiblilityMigrater.class.getName());
26 | private static final String SYNC_PLUGIN_CONFIGURATION_FILE_NAME = "io.alauda.jenkins.devops.sync.AlaudaSyncGlobalConfiguration.xml";
27 |
28 |
29 | public static KubernetesCluster migrateConfigurationFromSyncPlugin() {
30 | File configFile = new File(Jenkins.getInstance().getRootDir(), SYNC_PLUGIN_CONFIGURATION_FILE_NAME);
31 | if (!configFile.exists() || !configFile.isFile()) {
32 | return null;
33 | }
34 |
35 | try {
36 | Document configDocument = readFileToXMLDocument(configFile);
37 | Element documentElement = configDocument.getDocumentElement();
38 | if (documentElement == null) {
39 | return null;
40 | }
41 |
42 | KubernetesCluster cluster = new KubernetesCluster();
43 | NodeList trustCertsNodeList = configDocument.getElementsByTagName("trustCerts");
44 | if (trustCertsNodeList == null || trustCertsNodeList.getLength() == 0) {
45 | return null;
46 | }
47 | String trustCertsStr = trustCertsNodeList.item(0).getTextContent();
48 | if ("true".equals(trustCertsStr) || "false".equals(trustCertsStr)) {
49 | cluster.setSkipTlsVerify(Boolean.parseBoolean(trustCertsStr));
50 | } else {
51 | return null;
52 | }
53 | removeNodeList(trustCertsNodeList);
54 |
55 | NodeList serverNodeList = configDocument.getElementsByTagName("server");
56 | if (serverNodeList == null || serverNodeList.getLength() == 0) {
57 | return null;
58 | }
59 | cluster.setMasterUrl(serverNodeList.item(0).getTextContent());
60 | removeNodeList(serverNodeList);
61 |
62 | NodeList credentialsNodeList = configDocument.getElementsByTagName("credentialsId");
63 | if (credentialsNodeList != null && credentialsNodeList.getLength() > 0) {
64 | cluster.setCredentialsId(credentialsNodeList.item(0).getTextContent());
65 | removeNodeList(credentialsNodeList);
66 | }
67 |
68 | writeXMLDocumentToFile(configDocument, configFile);
69 | return cluster;
70 | } catch (ParserConfigurationException | IOException | TransformerException | SAXException e) {
71 | logger.log(Level.FINE, String.format("Unable to migrate configuration from old sync plugin, will skip it, reason: %s", e.getMessage()), e);
72 | }
73 | return null;
74 | }
75 |
76 | private static Document readFileToXMLDocument(File xml) throws ParserConfigurationException, IOException, SAXException {
77 | DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
78 | try {
79 | factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
80 | } catch (ParserConfigurationException e) {
81 | logger.log(Level.WARNING, "Failed to set FEATURE_SECURE_PROCESSING to true", e);
82 | }
83 | DocumentBuilder builder = factory.newDocumentBuilder();
84 |
85 | return builder.parse(xml);
86 | }
87 |
88 | private static void writeXMLDocumentToFile(Document document, File xml) throws TransformerException, IOException {
89 | Transformer transformer = newSecureTransformerFactory().newTransformer();
90 | transformer.setOutputProperty(OutputKeys.INDENT, "yes");
91 |
92 | StreamResult result = new StreamResult(new FileWriterWithEncoding(xml, StandardCharsets.UTF_8));
93 | DOMSource source = new DOMSource(document);
94 |
95 | transformer.transform(source, result);
96 | }
97 |
98 | private static void removeNodeList(NodeList nodeList) {
99 | if (nodeList == null) {
100 | return;
101 | }
102 | for (int i = 0; i < nodeList.getLength(); i++) {
103 | nodeList.item(i).getParentNode().removeChild(nodeList.item(i));
104 | }
105 | }
106 |
107 | private static TransformerFactory newSecureTransformerFactory() {
108 | TransformerFactory transformerFactory = TransformerFactory.newInstance();
109 | try {
110 | transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
111 | } catch (TransformerConfigurationException e) {
112 | e.printStackTrace();
113 | transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
114 | transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
115 | }
116 | return transformerFactory;
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | org.jenkins-ci.plugins
6 | plugin
7 | 2.36
8 |
9 |
10 | io.alauda.jenkins.plugins
11 | alauda-kubernetes-support
12 | 2.3.1-SNAPSHOT
13 | hpi
14 |
15 |
16 | 1.0
17 | -SNAPSHOT
18 | 2.83
19 | 8
20 |
21 |
22 | Alauda Kubernetes Suport Plugin
23 | This plugin allows users to config kubernetes and share configuration to other plugins.
24 |
25 |
26 | Alauda
27 | http://alauda.io
28 |
29 |
30 |
31 |
32 | MIT License
33 | https://opensource.org/licenses/MIT
34 |
35 |
36 |
37 |
38 |
39 | alaudaDevelopers
40 | Alauda Developers
41 | alauda
42 | http://alauda.io/
43 | devs@alauda.io
44 |
45 |
46 |
47 | https://wiki.jenkins.io/display/JENKINS/Alauda+Kubernetes+Support+Plugin
48 |
49 | scm:git:git://github.com/jenkinsci/${project.artifactId}-plugin.git
50 | scm:git:git@github.com:jenkinsci/${project.artifactId}-plugin.git
51 | https://github.com/jenkinsci/${project.artifactId}-plugin
52 | HEAD
53 |
54 |
55 |
56 |
57 | repo.jenkins-ci.org
58 | https://repo.jenkins-ci.org/public/
59 |
60 |
61 |
62 |
63 | repo.jenkins-ci.org
64 | https://repo.jenkins-ci.org/public/
65 |
66 |
67 |
68 |
69 |
70 | io.kubernetes
71 | client-java
72 | 6.0.1
73 |
74 |
75 | org.bouncycastle
76 | bcprov-jdk15on
77 |
78 |
79 | org.bouncycastle
80 | bcpkix-jdk15on
81 |
82 |
83 | org.bouncycastle
84 | bcpg-jdk15on
85 |
86 |
87 | org.bouncycastle
88 | bcprov-ext-jdk15on
89 |
90 |
91 |
92 |
93 | io.kubernetes
94 | client-java-extended
95 | 6.0.1
96 |
97 |
98 | org.bouncycastle
99 | bcprov-jdk15on
100 |
101 |
102 | org.bouncycastle
103 | bcpkix-jdk15on
104 |
105 |
106 | org.bouncycastle
107 | bcpg-jdk15on
108 |
109 |
110 | org.bouncycastle
111 | bcprov-ext-jdk15on
112 |
113 |
114 |
115 |
116 |
117 | org.bouncycastle
118 | bcprov-jdk15on
119 | 1.59
120 |
121 |
122 | org.bouncycastle
123 | bcpkix-jdk15on
124 | 1.59
125 |
126 |
127 | org.bouncycastle
128 | bcpg-jdk15on
129 | 1.59
130 |
131 |
132 | org.bouncycastle
133 | bcprov-ext-jdk15on
134 | 1.59
135 |
136 |
137 | org.jenkins-ci.plugins
138 | credentials
139 | 2.1.16
140 |
141 |
142 | com.squareup.okhttp3
143 | okhttp
144 | 3.12.0
145 |
146 |
147 | org.jenkins-ci.plugins
148 | plain-credentials
149 | 1.4
150 |
151 |
152 |
153 |
--------------------------------------------------------------------------------
/src/main/java/io/alauda/jenkins/devops/support/KubernetesCluster.java:
--------------------------------------------------------------------------------
1 | package io.alauda.jenkins.devops.support;
2 |
3 | import com.cloudbees.plugins.credentials.common.StandardListBoxModel;
4 | import hudson.Extension;
5 | import hudson.model.AbstractDescribableImpl;
6 | import hudson.model.Descriptor;
7 | import hudson.security.ACL;
8 | import hudson.util.FormValidation;
9 | import hudson.util.ListBoxModel;
10 | import io.alauda.jenkins.devops.support.client.Clients;
11 | import io.alauda.jenkins.devops.support.exception.KubernetesClientException;
12 | import io.kubernetes.client.ApiClient;
13 | import io.kubernetes.client.ApiException;
14 | import io.kubernetes.client.apis.CoreV1Api;
15 | import io.kubernetes.client.models.V1NamespaceList;
16 | import jenkins.model.Jenkins;
17 | import org.jenkinsci.plugins.plaincredentials.StringCredentials;
18 | import org.kohsuke.stapler.DataBoundConstructor;
19 | import org.kohsuke.stapler.DataBoundSetter;
20 | import org.kohsuke.stapler.QueryParameter;
21 |
22 | import java.util.Objects;
23 |
24 | public class KubernetesCluster extends AbstractDescribableImpl {
25 | private String masterUrl;
26 | private String credentialsId;
27 | private boolean skipTlsVerify = false;
28 | private String serverCertificateAuthority;
29 | private boolean defaultCluster = false;
30 | private boolean managerCluster = false;
31 |
32 | @DataBoundConstructor
33 | public KubernetesCluster() {
34 | }
35 |
36 | public String getMasterUrl() {
37 | return masterUrl;
38 | }
39 |
40 | @DataBoundSetter
41 | public void setMasterUrl(String masterUrl) {
42 | this.masterUrl = masterUrl;
43 | }
44 |
45 | public String getCredentialsId() {
46 | return credentialsId;
47 | }
48 |
49 | @DataBoundSetter
50 | public void setCredentialsId(String credentialId) {
51 | this.credentialsId = credentialId;
52 | }
53 |
54 | public boolean isSkipTlsVerify() {
55 | return skipTlsVerify;
56 | }
57 |
58 | @DataBoundSetter
59 | public void setSkipTlsVerify(boolean skipTlsVerify) {
60 | this.skipTlsVerify = skipTlsVerify;
61 | }
62 |
63 | public String getServerCertificateAuthority() {
64 | return serverCertificateAuthority;
65 | }
66 |
67 | @DataBoundSetter
68 | public void setServerCertificateAuthority(String serverCertificateAuthority) {
69 | this.serverCertificateAuthority = serverCertificateAuthority;
70 | }
71 |
72 | public boolean isDefaultCluster() {
73 | return defaultCluster;
74 | }
75 |
76 | @DataBoundSetter
77 | public void setDefaultCluster(boolean defaultCluster) {
78 | this.defaultCluster = defaultCluster;
79 | }
80 |
81 | public boolean isManagerCluster() {
82 | return managerCluster;
83 | }
84 |
85 | @DataBoundSetter
86 | public void setManagerCluster(boolean managerCluster) {
87 | this.managerCluster = managerCluster;
88 | }
89 |
90 | @Override
91 | public boolean equals(Object o) {
92 | if (this == o) return true;
93 | if (o == null || getClass() != o.getClass()) return false;
94 | KubernetesCluster cluster = (KubernetesCluster) o;
95 | return skipTlsVerify == cluster.skipTlsVerify &&
96 | defaultCluster == cluster.defaultCluster &&
97 | managerCluster == cluster.managerCluster &&
98 | Objects.equals(masterUrl, cluster.masterUrl) &&
99 | Objects.equals(credentialsId, cluster.credentialsId) &&
100 | Objects.equals(serverCertificateAuthority, cluster.serverCertificateAuthority);
101 | }
102 |
103 | @Override
104 | public int hashCode() {
105 | return Objects.hash(masterUrl, credentialsId, skipTlsVerify, serverCertificateAuthority, defaultCluster, managerCluster);
106 | }
107 |
108 | @Extension
109 | public static class AlaudaDevOpsK8sServerDescriptor extends Descriptor {
110 |
111 | public ListBoxModel doFillCredentialsIdItems(@QueryParameter String credentialsId) {
112 | if (credentialsId == null) {
113 | credentialsId = "";
114 | }
115 |
116 | if (!Jenkins.getInstance().hasPermission(Jenkins.ADMINISTER)) {
117 | return new StandardListBoxModel()
118 | .includeCurrentValue(credentialsId);
119 | }
120 |
121 | return new StandardListBoxModel()
122 | .includeEmptyValue()
123 | .includeAs(ACL.SYSTEM, Jenkins.getInstance(),
124 | StringCredentials.class)
125 | .includeCurrentValue(credentialsId);
126 | }
127 |
128 |
129 | public FormValidation doVerifyConnect(@QueryParameter String masterUrl,
130 | @QueryParameter String credentialsId,
131 | @QueryParameter String serverCertificateAuthority,
132 | @QueryParameter boolean skipTlsVerify) {
133 |
134 | Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
135 |
136 | ApiClient testClient;
137 | try {
138 | testClient = Clients.createClientFromConfig(masterUrl, credentialsId, serverCertificateAuthority, skipTlsVerify);
139 | } catch (KubernetesClientException e) {
140 | e.printStackTrace();
141 | return FormValidation.error(e.getMessage());
142 | }
143 |
144 | CoreV1Api api = new CoreV1Api(testClient);
145 | V1NamespaceList list;
146 | try {
147 | list = api.listNamespace(null, null, null, null, null, null, null, null);
148 | } catch (ApiException e) {
149 | return FormValidation.error(e.getMessage());
150 | }
151 |
152 | if (list == null) {
153 | return FormValidation.error(String.format("Unable to connect to cluster %s", masterUrl));
154 | }
155 |
156 | return FormValidation.ok(String.format("Connect to %s succeed", masterUrl));
157 | }
158 | }
159 | }
160 |
--------------------------------------------------------------------------------