├── Jenkinsfile
├── images
└── plugin-configuration.png
├── src
└── main
│ ├── resources
│ ├── index.jelly
│ └── io
│ │ └── alauda
│ │ └── jenkins
│ │ └── plugins
│ │ └── credentials
│ │ └── KubernetesCredentialsProviderConfiguration
│ │ └── config.jelly
│ ├── webapp
│ └── images
│ │ ├── 16x16
│ │ └── alauda.png
│ │ ├── 24x24
│ │ └── alauda.png
│ │ ├── 32x32
│ │ └── alauda.png
│ │ └── 48x48
│ │ └── alauda.png
│ └── java
│ └── io
│ └── alauda
│ └── jenkins
│ └── plugins
│ └── credentials
│ ├── metadata
│ ├── NamespaceProvider.java
│ ├── MetadataProvider.java
│ └── CredentialsWithMetadata.java
│ ├── CredentialsUtils.java
│ ├── rule
│ └── KubernetesSecretRule.java
│ ├── convertor
│ ├── ServiceAccountTokenCredentialsConverter.java
│ ├── OpaqueCredentialsConverter.java
│ ├── UsernamePasswordCredentialsConvertor.java
│ ├── CredentialsConversionException.java
│ └── SecretToCredentialConverter.java
│ ├── scope
│ ├── JenkinsRootScope.java
│ └── KubernetesSecretScope.java
│ ├── KubernetesCredentialsProviderConfiguration.java
│ ├── AlaudaKubernetesCredentialsStore.java
│ ├── SecretUtils.java
│ └── KubernetesCredentialsProvider.java
├── scripts
├── run.sh
├── restart.sh
└── upload.sh
├── sonar-project.properties
├── README.md
├── LICENSE
├── .gitignore
├── pom.xml
└── Alauda.Jenkinsfile
/Jenkinsfile:
--------------------------------------------------------------------------------
1 | buildPlugin()
--------------------------------------------------------------------------------
/images/plugin-configuration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alauda-devops-credentials-provider-plugin/master/images/plugin-configuration.png
--------------------------------------------------------------------------------
/src/main/resources/index.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 | This plugin will provide a credentials store backed by Kubernetes
4 |
5 |
--------------------------------------------------------------------------------
/src/main/webapp/images/16x16/alauda.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alauda-devops-credentials-provider-plugin/master/src/main/webapp/images/16x16/alauda.png
--------------------------------------------------------------------------------
/src/main/webapp/images/24x24/alauda.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alauda-devops-credentials-provider-plugin/master/src/main/webapp/images/24x24/alauda.png
--------------------------------------------------------------------------------
/src/main/webapp/images/32x32/alauda.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alauda-devops-credentials-provider-plugin/master/src/main/webapp/images/32x32/alauda.png
--------------------------------------------------------------------------------
/src/main/webapp/images/48x48/alauda.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/alauda-devops-credentials-provider-plugin/master/src/main/webapp/images/48x48/alauda.png
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/main/java/io/alauda/jenkins/plugins/credentials/metadata/NamespaceProvider.java:
--------------------------------------------------------------------------------
1 | package io.alauda.jenkins.plugins.credentials.metadata;
2 |
3 | import hudson.Extension;
4 | import io.kubernetes.client.models.V1Secret;
5 |
6 | @Extension
7 | public class NamespaceProvider implements MetadataProvider {
8 |
9 | public static final String NAMESPACE_METADATA = "namespace";
10 |
11 | @Override
12 | public void attach(V1Secret secret, CredentialsWithMetadata credentialsWithMetadata) {
13 | credentialsWithMetadata.addMetadata(NAMESPACE_METADATA, secret.getMetadata().getNamespace());
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/sonar-project.properties:
--------------------------------------------------------------------------------
1 | # must be unique in a given SonarQube instance
2 | sonar.projectKey=alauda-devops-credentials-provider
3 | # this is the name and version displayed in the SonarQube UI. Was mandatory prior to SonarQube 6.1.
4 | sonar.projectName=alauda-devops-credentials-provider-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/resources/io/alauda/jenkins/plugins/credentials/KubernetesCredentialsProviderConfiguration/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/main/java/io/alauda/jenkins/plugins/credentials/CredentialsUtils.java:
--------------------------------------------------------------------------------
1 | package io.alauda.jenkins.plugins.credentials;
2 |
3 | import com.cloudbees.plugins.credentials.CredentialsScope;
4 | import hudson.util.Secret;
5 | import io.kubernetes.client.models.V1ObjectMeta;
6 | import org.jenkinsci.plugins.plaincredentials.StringCredentials;
7 | import org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl;
8 |
9 | public final class CredentialsUtils {
10 |
11 | private CredentialsUtils() {}
12 |
13 | public static StringCredentials convertToStringCredentials(V1ObjectMeta meta, String token) {
14 | return new StringCredentialsImpl(CredentialsScope.GLOBAL, SecretUtils.getCredentialId(meta),
15 | SecretUtils.getCredentialDescription(meta), Secret.fromString(token));
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Alauda DevOps Credentials Provider plugin
2 |
3 |
4 | This plugin will sync secrets from Kubernetes to Jenkins and convert them to Credentials stored in memory.
5 |
6 | It provides four extension points for other plugins to implement to have more powerful functionality.
7 |
8 | 1. SecretToCredentialConverter defines which type of secret should be convert and how to convert it.
9 | 2. MetadataProvider allows adding metadata to Credentials
10 | 3. KubernetesSecretRule defines which secret should
11 | 4. KubernetesSecretScope defines where those secrets can be used
12 |
13 | ### Configuration
14 | 
15 |
16 | - Global Namespaces - secrets in these namespaces can be used globally
17 | - Label Selector - label selector to watch a group of secrets
--------------------------------------------------------------------------------
/src/main/java/io/alauda/jenkins/plugins/credentials/metadata/MetadataProvider.java:
--------------------------------------------------------------------------------
1 | package io.alauda.jenkins.plugins.credentials.metadata;
2 |
3 | import hudson.ExtensionList;
4 | import hudson.ExtensionPoint;
5 | import io.kubernetes.client.models.V1Secret;
6 |
7 | /**
8 | * Provide metadata sources that implementations can get metadata from sources and add them to credentials.
9 | */
10 | public interface MetadataProvider extends ExtensionPoint {
11 |
12 | /**
13 | * Get metadata from K8s secret and add them to its corresponding credentials
14 | * @param secret Secret in K8s
15 | * @param credentialsWithMetadata credentials that converted from Secret
16 | */
17 | void attach(V1Secret secret, CredentialsWithMetadata credentialsWithMetadata);
18 |
19 | static ExtensionList all() {
20 | return ExtensionList.lookup(MetadataProvider.class);
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/io/alauda/jenkins/plugins/credentials/metadata/CredentialsWithMetadata.java:
--------------------------------------------------------------------------------
1 | package io.alauda.jenkins.plugins.credentials.metadata;
2 |
3 | import com.cloudbees.plugins.credentials.common.IdCredentials;
4 |
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | public class CredentialsWithMetadata {
9 |
10 | private C credentials;
11 | private Map metadata = new HashMap<>();
12 |
13 | public CredentialsWithMetadata(C credentials) {
14 | this.credentials = credentials;
15 | }
16 |
17 | public C getCredentials() {
18 | return credentials;
19 | }
20 |
21 | public void setCredentials(C credentials) {
22 | this.credentials = credentials;
23 | }
24 |
25 | public void addMetadata(String key, String value) {
26 | metadata.put(key, value);
27 | }
28 |
29 | public String getMetadata(String key) {
30 | return metadata.get(key);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/io/alauda/jenkins/plugins/credentials/rule/KubernetesSecretRule.java:
--------------------------------------------------------------------------------
1 | package io.alauda.jenkins.plugins.credentials.rule;
2 |
3 | import hudson.ExtensionList;
4 | import hudson.ExtensionPoint;
5 | import io.kubernetes.client.models.V1Secret;
6 |
7 | /**
8 | * Defines a series of rules that apply to secret
9 | */
10 | public interface KubernetesSecretRule extends ExtensionPoint {
11 |
12 | /**
13 | * Check if the secret should be excluded.
14 | * @param secret secret will be checked.
15 | * @return true, if the secret should be excluded.
16 | */
17 | boolean exclude(V1Secret secret);
18 |
19 | static ExtensionList all() {
20 | return ExtensionList.lookup(KubernetesSecretRule.class);
21 | }
22 |
23 | static boolean shouldExclude(V1Secret secret) {
24 | ExtensionList filters = all();
25 |
26 | return filters.stream().anyMatch(e -> e.exclude(secret));
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/main/java/io/alauda/jenkins/plugins/credentials/convertor/ServiceAccountTokenCredentialsConverter.java:
--------------------------------------------------------------------------------
1 | package io.alauda.jenkins.plugins.credentials.convertor;
2 |
3 | import com.cloudbees.plugins.credentials.common.IdCredentials;
4 | import hudson.Extension;
5 | import io.alauda.jenkins.plugins.credentials.CredentialsUtils;
6 | import io.alauda.jenkins.plugins.credentials.SecretUtils;
7 | import io.kubernetes.client.models.V1Secret;
8 |
9 | @Extension
10 | public class ServiceAccountTokenCredentialsConverter extends SecretToCredentialConverter {
11 | private static final String SERVICE_ACCOUNT_TOKEN_TYPE = "kubernetes.io/service-account-token";
12 |
13 |
14 | @Override
15 | public boolean canConvert(String type) {
16 | return SERVICE_ACCOUNT_TOKEN_TYPE.equals(type);
17 | }
18 |
19 | @Override
20 | public IdCredentials convert(V1Secret secret) throws CredentialsConversionException {
21 | SecretUtils.requireNonNull(secret.getData(), "kubernetes.io/service-account-token definition contains no data");
22 | String token = SecretUtils.getNonNullSecretData(secret, "token", "kubernetes.io/service-account-token credential is missing the token");
23 |
24 | return CredentialsUtils.convertToStringCredentials(secret.getMetadata(), token);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/io/alauda/jenkins/plugins/credentials/convertor/OpaqueCredentialsConverter.java:
--------------------------------------------------------------------------------
1 | package io.alauda.jenkins.plugins.credentials.convertor;
2 |
3 | import com.cloudbees.plugins.credentials.common.IdCredentials;
4 | import hudson.Extension;
5 | import io.alauda.jenkins.plugins.credentials.CredentialsUtils;
6 | import io.alauda.jenkins.plugins.credentials.SecretUtils;
7 | import io.kubernetes.client.models.V1Secret;
8 |
9 | import java.util.Map;
10 | import java.util.Optional;
11 |
12 | @Extension
13 | public class OpaqueCredentialsConverter extends SecretToCredentialConverter {
14 | private static final String OPAQUE_TYPE = "Opaque";
15 |
16 | @Override
17 | public boolean canConvert(String type) {
18 | return OPAQUE_TYPE.equals(type);
19 | }
20 |
21 | @Override
22 | public IdCredentials convert(V1Secret secret) throws CredentialsConversionException {
23 | Map data = secret.getData();
24 | if(data == null) {
25 | return null;
26 | }
27 |
28 | Optional tokenOpt = SecretUtils.getOptionalSecretData(secret, "token", "");
29 | if(tokenOpt.isPresent()) {
30 | String token = tokenOpt.get();
31 | return CredentialsUtils.convertToStringCredentials(secret.getMetadata(), token);
32 | }
33 |
34 | return null;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/io/alauda/jenkins/plugins/credentials/scope/JenkinsRootScope.java:
--------------------------------------------------------------------------------
1 | package io.alauda.jenkins.plugins.credentials.scope;
2 |
3 | import hudson.Extension;
4 | import hudson.model.ItemGroup;
5 | import io.alauda.jenkins.plugins.credentials.KubernetesCredentialsProviderConfiguration;
6 | import io.alauda.jenkins.plugins.credentials.metadata.CredentialsWithMetadata;
7 | import io.alauda.jenkins.plugins.credentials.metadata.NamespaceProvider;
8 | import jenkins.model.Jenkins;
9 | import org.apache.commons.lang.StringUtils;
10 |
11 | import java.util.Arrays;
12 |
13 | @Extension
14 | public class JenkinsRootScope implements KubernetesSecretScope {
15 | @Override
16 | public boolean isInScope(ItemGroup owner) {
17 | return owner == Jenkins.getInstance();
18 | }
19 |
20 | @Override
21 | public boolean shouldShowInScope(ItemGroup owner, CredentialsWithMetadata credentialsWithMetadata) {
22 | if (!isInScope(owner)) {
23 | return false;
24 | }
25 |
26 | String namespace = credentialsWithMetadata.getMetadata(NamespaceProvider.NAMESPACE_METADATA);
27 | if (StringUtils.isEmpty(namespace)) {
28 | return false;
29 | }
30 |
31 | String globalNamespaces = KubernetesCredentialsProviderConfiguration.get().getGlobalNamespaces();
32 | if (globalNamespaces == null) {
33 | return false;
34 | }
35 |
36 | return Arrays.asList(globalNamespaces.split(",")).contains(namespace);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/io/alauda/jenkins/plugins/credentials/scope/KubernetesSecretScope.java:
--------------------------------------------------------------------------------
1 | package io.alauda.jenkins.plugins.credentials.scope;
2 |
3 | import hudson.ExtensionList;
4 | import hudson.ExtensionPoint;
5 | import hudson.model.ItemGroup;
6 | import io.alauda.jenkins.plugins.credentials.metadata.CredentialsWithMetadata;
7 |
8 | import java.util.List;
9 | import java.util.stream.Collectors;
10 |
11 | /**
12 | * Scope defines where we can see credentials and what credentials we should display in these places.
13 | */
14 | public interface KubernetesSecretScope extends ExtensionPoint {
15 |
16 | /**
17 | * Check if this scope should include ItemGroup.
18 | * @param owner ItemGroup will be checked.
19 | * @return true if the scope includes this ItemGroup.
20 | */
21 | boolean isInScope(ItemGroup owner);
22 |
23 | /**
24 | * Check if the credentials should show under the ItemGroup
25 | * @return true if the credentials should show in the ItemGroup
26 | */
27 | boolean shouldShowInScope(ItemGroup owner, CredentialsWithMetadata credentialsWithMetadata);
28 |
29 | static ExtensionList all() {
30 | return ExtensionList.lookup(KubernetesSecretScope.class);
31 | }
32 |
33 | static boolean hasMatchedScope(ItemGroup owner) {
34 | ExtensionList scopes = all();
35 |
36 | return scopes.stream().anyMatch(s -> s.isInScope(owner));
37 | }
38 |
39 | static List matchedScopes(ItemGroup owner) {
40 | ExtensionList scopes = all();
41 |
42 | return scopes.stream().filter(s -> s.isInScope(owner)).collect(Collectors.toList());
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/io/alauda/jenkins/plugins/credentials/KubernetesCredentialsProviderConfiguration.java:
--------------------------------------------------------------------------------
1 | package io.alauda.jenkins.plugins.credentials;
2 |
3 | import hudson.Extension;
4 | import jenkins.model.GlobalConfiguration;
5 | import net.sf.json.JSONObject;
6 | import org.kohsuke.stapler.DataBoundSetter;
7 | import org.kohsuke.stapler.StaplerRequest;
8 |
9 | import javax.annotation.Nonnull;
10 |
11 | @Extension(ordinal = 202)
12 | public class KubernetesCredentialsProviderConfiguration extends GlobalConfiguration {
13 |
14 | private String globalNamespaces = "global-credentials";
15 | private String labelSelector;
16 |
17 | public static KubernetesCredentialsProviderConfiguration get() {
18 | return GlobalConfiguration.all().get(KubernetesCredentialsProviderConfiguration.class);
19 |
20 | }
21 |
22 | public KubernetesCredentialsProviderConfiguration() {
23 | load();
24 | }
25 |
26 | @Override
27 | public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
28 | req.bindJSON(this, json);
29 | save();
30 | return true;
31 | }
32 |
33 | public String getGlobalNamespaces() {
34 | return globalNamespaces;
35 | }
36 |
37 | @DataBoundSetter
38 | public void setGlobalNamespaces(String globalNamespaces) {
39 | this.globalNamespaces = globalNamespaces;
40 | }
41 |
42 | public String getLabelSelector() {
43 | return labelSelector;
44 | }
45 |
46 | @DataBoundSetter
47 | public void setLabelSelector(String labelSelector) {
48 | this.labelSelector = labelSelector;
49 | }
50 |
51 | @Nonnull
52 | @Override
53 | public String getDisplayName() {
54 | return "Alauda Credential Provider";
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/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-devops-credentials-provider.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/main/java/io/alauda/jenkins/plugins/credentials/convertor/UsernamePasswordCredentialsConvertor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2018 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 | package io.alauda.jenkins.plugins.credentials.convertor;
25 |
26 | import com.cloudbees.plugins.credentials.CredentialsScope;
27 | import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl;
28 | import hudson.Extension;
29 | import io.alauda.jenkins.plugins.credentials.SecretUtils;
30 | import io.kubernetes.client.models.V1Secret;
31 |
32 | /**
33 | * SecretToCredentialConvertor that converts {@link UsernamePasswordCredentialsImpl}.
34 | */
35 | @Extension
36 | public class UsernamePasswordCredentialsConvertor extends SecretToCredentialConverter {
37 |
38 | @Override
39 | public boolean canConvert(String type) {
40 | return "kubernetes.io/basic-auth".equals(type);
41 | }
42 |
43 | @Override
44 | public UsernamePasswordCredentialsImpl convert(V1Secret secret) throws CredentialsConversionException {
45 | SecretUtils.requireNonNull(secret.getData(), "kubernetes.io/basic-auth definition contains no data");
46 |
47 | String username = SecretUtils.getNonNullSecretData(secret, "username", "kubernetes.io/basic-auth credential is missing the username");
48 | String password = SecretUtils.getNonNullSecretData(secret, "password", "kubernetes.io/basic-auth credential is missing the password");
49 |
50 | return new UsernamePasswordCredentialsImpl(CredentialsScope.GLOBAL, SecretUtils.getCredentialId(secret), SecretUtils.getCredentialDescription(secret), username, password);
51 |
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/io/alauda/jenkins/plugins/credentials/convertor/CredentialsConversionException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2018 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 | package io.alauda.jenkins.plugins.credentials.convertor;
25 |
26 | import javax.annotation.Nullable;
27 |
28 | /**
29 | * Exception thrown when a credential could not be converted.
30 | */
31 | public class CredentialsConversionException extends Exception {
32 |
33 | private static final long serialVersionUID = 1L;
34 |
35 | /**
36 | * Constructs a new CredentialsConversionException with the specified detail message.
37 | *
38 | * The cause is not initialized, and may subsequently be initialized by a call to {@link #initCause}.
39 | *
40 | * @param message the detail message. The detail message is saved for later retrieval by the {@link #getMessage()}
41 | * method.
42 | */
43 | public CredentialsConversionException(String message) {
44 | super(message);
45 | }
46 |
47 | /**
48 | * Constructs a new CredentialsConversionException with the specified detail message and cause.
49 | *
50 | * Note that the detail message associated with {@code cause} is not automatically incorporated in this
51 | * exception's detail message.
52 | *
53 | * @param message the detail message (which is saved for later retrieval by the {@link #getMessage()} method).
54 | * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method). (A {@code null}
55 | * value is permitted, and indicates that the cause is nonexistent or unknown.)
56 | */
57 | public CredentialsConversionException(String message, @Nullable Throwable cause) {
58 | super(message, cause);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### JetBrains template
3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
5 |
6 | # User-specific stuff
7 | .idea/**/workspace.xml
8 | .idea/**/tasks.xml
9 | .idea/**/dictionaries
10 | .idea/**/shelf
11 |
12 | # Sensitive or high-churn files
13 | .idea/**/dataSources/
14 | .idea/**/dataSources.ids
15 | .idea/**/dataSources.local.xml
16 | .idea/**/sqlDataSources.xml
17 | .idea/**/dynamic.xml
18 | .idea/**/uiDesigner.xml
19 | .idea/**/dbnavigator.xml
20 |
21 | # Gradle
22 | .idea/**/gradle.xml
23 | .idea/**/libraries
24 |
25 | # CMake
26 | cmake-build-debug/
27 | cmake-build-release/
28 |
29 | # Mongo Explorer plugin
30 | .idea/**/mongoSettings.xml
31 |
32 | # File-based project format
33 | *.iws
34 |
35 | # IntelliJ
36 | out/
37 |
38 | # mpeltonen/sbt-idea plugin
39 | .idea_modules/
40 |
41 | # JIRA plugin
42 | atlassian-ide-plugin.xml
43 |
44 | # Cursive Clojure plugin
45 | .idea/replstate.xml
46 |
47 | # Crashlytics plugin (for Android Studio and IntelliJ)
48 | com_crashlytics_export_strings.xml
49 | crashlytics.properties
50 | crashlytics-build.properties
51 | fabric.properties
52 |
53 | # Editor-based Rest Client
54 | .idea/httpRequests
55 | ### Java template
56 | # Compiled class file
57 | *.class
58 |
59 | # Log file
60 | *.log
61 |
62 | # BlueJ files
63 | *.ctxt
64 |
65 | # Mobile Tools for Java (J2ME)
66 | .mtj.tmp/
67 |
68 | # Package Files #
69 | *.jar
70 | *.war
71 | *.nar
72 | *.ear
73 | *.zip
74 | *.tar.gz
75 | *.rar
76 |
77 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
78 | hs_err_pid*
79 | ### Maven template
80 | target/
81 | pom.xml.tag
82 | pom.xml.releaseBackup
83 | pom.xml.versionsBackup
84 | pom.xml.next
85 | release.properties
86 | dependency-reduced-pom.xml
87 | buildNumber.properties
88 | .mvn/timing.properties
89 |
90 | # Avoid ignoring Maven wrapper jar file (.jar files are usually ignored)
91 | !/.mvn/wrapper/maven-wrapper.jar
92 | ### macOS template
93 | # General
94 | .DS_Store
95 | .AppleDouble
96 | .LSOverride
97 |
98 | # Icon must end with two \r
99 | Icon
100 |
101 | # Thumbnails
102 | ._*
103 |
104 | # Files that might appear in the root of a volume
105 | .DocumentRevisions-V100
106 | .fseventsd
107 | .Spotlight-V100
108 | .TemporaryItems
109 | .Trashes
110 | .VolumeIcon.icns
111 | .com.apple.timemachine.donotpresent
112 |
113 | # Directories potentially created on remote AFP share
114 | .AppleDB
115 | .AppleDesktop
116 | Network Trash Folder
117 | Temporary Items
118 | .apdisk
119 | ### Example user template template
120 | ### Example user template
121 |
122 | # IntelliJ project files
123 | .idea
124 | *.iml
125 | out
126 | gen
127 | work
128 |
129 | scripts/env.sh
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | org.jenkins-ci.plugins
6 | plugin
7 | 3.4
8 |
9 |
10 | io.alauda.jenkins.plugins
11 | alauda-devops-credentials-provider
12 | 2.3.1-SNAPSHOT
13 | hpi
14 |
15 | 2.83
16 | 8
17 |
18 | Alauda DevOps Credentials Provider Plugin
19 | This plugin will provide a credentials store backed by Kubernetes
20 |
21 |
22 |
23 | MIT License
24 | https://opensource.org/licenses/MIT
25 |
26 |
27 |
28 |
29 |
30 | alaudaDevelopers
31 | Alauda Developers
32 | alauda
33 | http://alauda.io/
34 | devs@alauda.io
35 |
36 |
37 |
38 | https://wiki.jenkins.io/display/JENKINS/Alauda+DevOps+Credentials+Provider+Plugin
39 |
40 | scm:git:git://github.com/jenkinsci/${project.artifactId}-plugin.git
41 | scm:git:git@github.com:jenkinsci/${project.artifactId}-plugin.git
42 | https://github.com/jenkinsci/${project.artifactId}-plugin
43 | HEAD
44 |
45 |
46 |
47 |
48 | repo.jenkins-ci.org
49 | https://repo.jenkins-ci.org/public/
50 |
51 |
52 |
53 |
54 | repo.jenkins-ci.org
55 | https://repo.jenkins-ci.org/public/
56 |
57 |
58 |
59 |
60 |
61 | org.jenkins-ci.plugins
62 | credentials
63 | 2.1.16
64 |
65 |
66 | org.jenkins-ci.plugins
67 | cloudbees-folder
68 | 6.1.2
69 |
70 |
71 | io.alauda.jenkins.plugins
72 | alauda-kubernetes-support
73 | 2.3.0
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/src/main/java/io/alauda/jenkins/plugins/credentials/convertor/SecretToCredentialConverter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2018 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 | package io.alauda.jenkins.plugins.credentials.convertor;
25 |
26 | import com.cloudbees.plugins.credentials.common.IdCredentials;
27 | import hudson.ExtensionList;
28 | import hudson.ExtensionPoint;
29 | import io.kubernetes.client.models.V1Secret;
30 |
31 | /**
32 | * Class that converts a secret of a given type to an {@link IdCredentials}.
33 | */
34 | public abstract class SecretToCredentialConverter implements ExtensionPoint {
35 |
36 | /**
37 | * Check if this converter can transform secrets of a given type.
38 | * @param type the type of secret. This is normally the symbol attached to the credential type by the credential binding plugin.
39 | * @return {@code true} iff this {@code SecretToCredentialConvertor} can convert secrets of the specified type.
40 | */
41 | public abstract boolean canConvert(String type);
42 |
43 | /**
44 | * Convert the given {@code Secret} to an {@code IdCredential}.
45 | * This will only be called for a secret of a type that the class has previously returned {@code true} from {@link #canConvert(String)}.
46 | * @param secret the Secret to convert.
47 | * @throws CredentialsConversionException if the Secret could not be converted.
48 | * @return the IdCredentials created from the secret.
49 | */
50 | public abstract IdCredentials convert(V1Secret secret) throws CredentialsConversionException;
51 |
52 | /**
53 | * Helper to obtain all the implementations of this {@code ExtensionPoint}
54 | * @return the ExtensionList containing all of the implementations.
55 | */
56 | public static ExtensionList all() {
57 | return ExtensionList.lookup(SecretToCredentialConverter.class);
58 | }
59 |
60 | /**
61 | * Helper to obtain the SecretToCredentialConvertor that can convert this type of secret.
62 | * @param type the type of the secret to convert.
63 | * @return the SecretToCredentialConvertor to use for converting the secret.
64 | */
65 | public static SecretToCredentialConverter lookup(String type) {
66 | ExtensionList all = all();
67 | for (SecretToCredentialConverter stcc : all) {
68 | if (stcc.canConvert(type)) {
69 | return stcc;
70 | }
71 | }
72 | return null;
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/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-devops-credentials-provider-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-devops-credentials-provider-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 | // sonar scan
93 | stage('Sonar') {
94 | steps {
95 | script {
96 | container('tools') {
97 | deploy.scan(
98 | REPOSITORY,
99 | GIT_BRANCH,
100 | SONARQUBE_SCM_CREDENTIALS,
101 | FOLDER,
102 | DEBUG,
103 | OWNER,
104 | SCM_FEEDBACK_ACCOUNT).start()
105 | }
106 | }
107 | }
108 | }
109 | }
110 |
111 | // (optional)
112 | // happens at the end of the pipeline
113 | post {
114 | // 成功
115 | success {
116 | echo "Horay!"
117 | script {
118 | deploy.notificationSuccess(DEPLOYMENT, DINGDING_BOT, "流水线完成了", RELEASE_BUILD)
119 | }
120 | }
121 | // 失败
122 | failure {
123 | // check the npm log
124 | // fails lets check if it
125 | script {
126 | echo "damn!"
127 | deploy.notificationFailed(DEPLOYMENT, DINGDING_BOT, "流水线失败了", RELEASE_BUILD)
128 | }
129 | }
130 | always { junit allowEmptyResults: true, testResults: '**/target/surefire-reports/**/*.xml' }
131 | }
132 | }
133 |
134 |
--------------------------------------------------------------------------------
/src/main/java/io/alauda/jenkins/plugins/credentials/AlaudaKubernetesCredentialsStore.java:
--------------------------------------------------------------------------------
1 | package io.alauda.jenkins.plugins.credentials;
2 |
3 | import com.cloudbees.plugins.credentials.Credentials;
4 | import com.cloudbees.plugins.credentials.CredentialsProvider;
5 | import com.cloudbees.plugins.credentials.CredentialsStore;
6 | import com.cloudbees.plugins.credentials.CredentialsStoreAction;
7 | import com.cloudbees.plugins.credentials.domains.Domain;
8 | import hudson.model.ItemGroup;
9 | import hudson.model.ModelObject;
10 | import hudson.security.ACL;
11 | import hudson.security.Permission;
12 | import jenkins.model.Jenkins;
13 | import org.acegisecurity.Authentication;
14 | import org.jenkins.ui.icon.Icon;
15 | import org.jenkins.ui.icon.IconSet;
16 | import org.jenkins.ui.icon.IconType;
17 | import org.kohsuke.stapler.export.ExportedBean;
18 |
19 | import javax.annotation.Nonnull;
20 | import java.util.Collections;
21 | import java.util.List;
22 |
23 | public class AlaudaKubernetesCredentialsStore extends CredentialsStore {
24 | private final KubernetesCredentialsProvider provider;
25 | private final AlaudaKubernetesCredentialsStoreAction action = new AlaudaKubernetesCredentialsStoreAction(this);
26 |
27 | private ItemGroup owner;
28 |
29 | public AlaudaKubernetesCredentialsStore(KubernetesCredentialsProvider provider, ItemGroup owner) {
30 | super(KubernetesCredentialsProvider.class);
31 | this.provider = provider;
32 | this.owner = owner;
33 | }
34 |
35 |
36 | @Nonnull
37 | @Override
38 | public ModelObject getContext() {
39 | return owner;
40 | }
41 |
42 | @Override
43 | public boolean hasPermission(@Nonnull Authentication a, @Nonnull Permission permission) {
44 | return CredentialsProvider.VIEW.equals(permission) &&
45 | Jenkins.getInstance().getACL().hasPermission(a, permission);
46 | }
47 |
48 | @Nonnull
49 | @Override
50 | public List getCredentials(@Nonnull Domain domain) {
51 | if (Domain.global().equals(domain) && Jenkins.getInstance().hasPermission(CredentialsProvider.VIEW))
52 | return provider.getCredentialsWithinScope(Credentials.class, owner, ACL.SYSTEM);
53 | return Collections.emptyList();
54 | }
55 |
56 | @Override
57 | public boolean addCredentials(@Nonnull Domain domain, @Nonnull Credentials credentials) {
58 | return false;
59 | }
60 |
61 | @Override
62 | public boolean removeCredentials(@Nonnull Domain domain, @Nonnull Credentials credentials) {
63 | return false;
64 | }
65 |
66 | @Override
67 | public boolean updateCredentials(@Nonnull Domain domain, @Nonnull Credentials current, @Nonnull Credentials replacement) {
68 | return false;
69 | }
70 |
71 | @Nonnull
72 | @Override
73 | public CredentialsStoreAction getStoreAction() {
74 | return action;
75 | }
76 |
77 | /**
78 | * Expose the store.
79 | */
80 | @ExportedBean
81 | public static class AlaudaKubernetesCredentialsStoreAction extends CredentialsStoreAction {
82 |
83 | private final AlaudaKubernetesCredentialsStore store;
84 |
85 | private AlaudaKubernetesCredentialsStoreAction(AlaudaKubernetesCredentialsStore store) {
86 | this.store = store;
87 | addIcons();
88 | }
89 |
90 | @Override
91 | @Nonnull
92 | public CredentialsStore getStore() {
93 | return store;
94 | }
95 |
96 | @Override
97 | public String getDisplayName() {
98 | return "Alauda DevOps";
99 | }
100 |
101 | private void addIcons() {
102 | IconSet.icons.addIcon(new Icon("icon-credentials-alauda-store icon-sm",
103 | "alauda-devops-credentials-provider/images/16x16/alauda.png",
104 | Icon.ICON_SMALL_STYLE, IconType.PLUGIN));
105 | IconSet.icons.addIcon(new Icon("icon-credentials-alauda-store icon-md",
106 | "alauda-devops-credentials-provider/images/24x24/alauda.png",
107 | Icon.ICON_MEDIUM_STYLE, IconType.PLUGIN));
108 | IconSet.icons.addIcon(new Icon("icon-credentials-alauda-store icon-lg",
109 | "alauda-devops-credentials-provider/images/32x32/alauda.png",
110 | Icon.ICON_LARGE_STYLE, IconType.PLUGIN));
111 | IconSet.icons.addIcon(new Icon("icon-credentials-alauda-store icon-xlg",
112 | "alauda-devops-credentials-provider/images/48x48/alauda.png",
113 | Icon.ICON_XLARGE_STYLE, IconType.PLUGIN));
114 | }
115 |
116 | @Override
117 | public String getIconFileName() {
118 | return isVisible()
119 | ? "/plugin/alauda-devops-credentials-provider/images/32x32/alauda.png"
120 | : null;
121 | }
122 |
123 | @Override
124 | public String getIconClassName() {
125 | return isVisible()
126 | ? "icon-credentials-alauda-store"
127 | : null;
128 | }
129 |
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/main/java/io/alauda/jenkins/plugins/credentials/SecretUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2018 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 | package io.alauda.jenkins.plugins.credentials;
25 |
26 | import edu.umd.cs.findbugs.annotations.CheckForNull;
27 | import edu.umd.cs.findbugs.annotations.Nullable;
28 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
29 | import io.alauda.jenkins.plugins.credentials.convertor.CredentialsConversionException;
30 | import io.kubernetes.client.models.V1ObjectMeta;
31 | import io.kubernetes.client.models.V1Secret;
32 | import org.kohsuke.accmod.Restricted;
33 | import org.kohsuke.accmod.restrictions.NoExternalUse;
34 |
35 | import java.nio.ByteBuffer;
36 | import java.nio.CharBuffer;
37 | import java.nio.charset.CharacterCodingException;
38 | import java.nio.charset.CharsetDecoder;
39 | import java.nio.charset.CodingErrorAction;
40 | import java.nio.charset.StandardCharsets;
41 | import java.util.Base64;
42 | import java.util.Map;
43 | import java.util.Optional;
44 | import java.util.logging.Level;
45 | import java.util.logging.Logger;
46 |
47 | /**
48 | * Collection of utilities for working with {@link V1Secret}s.
49 | * Note: API may be subject to change.
50 | */
51 | public final class SecretUtils {
52 |
53 | private static final Logger LOG = Logger.getLogger(SecretUtils.class.getName());
54 |
55 | /** Optional Kubernetes annotation for the credential description */
56 | private static final String JENKINS_IO_CREDENTIALS_DESCRIPTION_ANNOTATION = "jenkins.io/credentials-description";
57 |
58 | /** Annotation prefix for the optional custom mapping of data */
59 | private static final String JENKINS_IO_CREDENTIALS_KEYBINDING_ANNOTATION_PREFIX = "jenkins.io/credentials-keybinding-";
60 |
61 | public static final String JENKINS_IO_CREDENTIALS_TYPE_LABEL = "jenkins.io/credentials-type";
62 |
63 | private SecretUtils() {}
64 |
65 | /**
66 | * Convert a String representation of the base64 encoded bytes of a UTF-8 String back to a String.
67 | * @param s the base64 encoded String representation of the bytes.
68 | * @return the String or {@code null} if the string could not be converted.
69 | */
70 | @CheckForNull
71 | @Restricted(NoExternalUse.class) // API is not yet concrete
72 | public static String base64DecodeToString(String s) {
73 | byte[] bytes = base64Decode(s);
74 | if (bytes != null) {
75 | try {
76 | CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
77 | decoder.onMalformedInput(CodingErrorAction.REPORT);
78 | decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
79 | CharBuffer decode = decoder.decode(ByteBuffer.wrap(bytes));
80 | return decode.toString();
81 | } catch (CharacterCodingException ex) {
82 | LOG.log(Level.WARNING, "failed to covert Secret, is this a valid UTF-8 string? {0}", ex.getMessage());
83 | }
84 | }
85 | return null;
86 | }
87 |
88 | /**
89 | * Convert a String representation of the base64 encoded bytes back to a byte[].
90 | * @param s the base64 encoded representation of the bytes.
91 | * @return the byte[] or {@code null} if the string could not be converted.
92 | */
93 | @CheckForNull
94 | @Restricted(NoExternalUse.class) // API is not yet concrete
95 | public static byte[] base64Decode(String s) {
96 | try {
97 | return Base64.getDecoder().decode(s);
98 | } catch (IllegalArgumentException ex) {
99 | LOG.log(Level.WARNING, "failed to base64decode Secret, is the format valid? {0}", ex.getMessage());
100 | }
101 | return null;
102 | }
103 |
104 | /**
105 | * Obtain the credential ID from a given {@code Secret}.
106 | * @param s the secret whose id we want to obtain.
107 | * @return the credential ID for a given secret.
108 | */
109 | public static String getCredentialId(V1Secret s) {
110 | // we must have a metadata as the label that identifies this as a Jenkins credential needs to be present
111 | return getCredentialId(s.getMetadata());
112 | }
113 |
114 | public static String getCredentialId(V1ObjectMeta meta) {
115 | // we must have a metadata as the label that identifies this as a Jenkins credential needs to be present
116 | return meta.getNamespace() + "-" + meta.getName();
117 | }
118 |
119 | /**
120 | * Obtain the credential description from a given {@code Secret}.
121 | * @param s the secret whose description we want to obtain.
122 | * @return the credential description for a given secret.
123 | */
124 | @CheckForNull
125 | public static String getCredentialDescription(V1Secret s) {
126 | return getCredentialDescription(s.getMetadata());
127 | }
128 |
129 | public static String getCredentialDescription(V1ObjectMeta meta) {
130 | // we must have a metadata as the label that identifies this as a Jenkins credential needs to be present
131 | Map annotations = meta.getAnnotations();
132 | if (annotations != null) {
133 | return annotations.get(JENKINS_IO_CREDENTIALS_DESCRIPTION_ANNOTATION);
134 | }
135 | return null;
136 | }
137 |
138 | /**
139 | * Checks that {@code obj} is not {@code null}.
140 | * @param obj the Object to check for {@code null}.
141 | * @param exceptionMessage detail message to be used in the event that a CredentialsConversionException is thrown.
142 | * @param the type of the obj.
143 | * @return {@code obj} if not {@code null}.
144 | * @throws CredentialsConversionException iff {@code obj} is {@code null}.
145 | */
146 | public static T requireNonNull(@Nullable T obj, String exceptionMessage) throws CredentialsConversionException {
147 | if (obj == null) {
148 | throw new CredentialsConversionException(exceptionMessage);
149 | }
150 | return obj;
151 | }
152 |
153 | /**
154 | * Checks that {@code obj} is not {@code null}.
155 | * @param obj the Object to check for {@code null}.
156 | * @param exceptionMessage detail message to be used in the event that a CredentialsConversionException is thrown.
157 | * @param mapped an optional mapping (adds a {@code "mapped to " + mapped} to the exception message if this is non null.
158 | * @param the type of the obj.
159 | * @return {@code obj} if not {@code null}.
160 | * @throws CredentialsConversionException iff {@code obj} is {@code null}.
161 | */
162 | public static T requireNonNull(@Nullable T obj, String exceptionMessage, @Nullable String mapped) throws CredentialsConversionException {
163 | if (obj == null) {
164 | if (mapped != null) {
165 | throw new CredentialsConversionException(exceptionMessage.concat(" (mapped to " + mapped + ")"));
166 | }
167 | throw new CredentialsConversionException(exceptionMessage);
168 | }
169 | return obj;
170 | }
171 |
172 |
173 | /**
174 | * Get the data for the specified key (or the mapped key if key is mapped), or throw a
175 | * CredentialsConversionException if the data for the given key was not present..
176 | *
177 | * @param s the Secret
178 | * @param key the key to get the data for (which may be mapped to another key).
179 | * @param exceptionMessage the detailMessage of the exception if the data for the key (or mapped key) was not
180 | * present.
181 | * @return The data for the given key.
182 | * @throws CredentialsConversionException if the data was not present.
183 | */
184 | @SuppressFBWarnings(value= {"ES_COMPARING_PARAMETER_STRING_WITH_EQ"}, justification="the string will be the same string if not mapped")
185 | public static String getNonNullSecretData(V1Secret s, String key, String exceptionMessage) throws CredentialsConversionException {
186 | String mappedKey = getKeyName(s, key);
187 | if (mappedKey.equals(key)) { // use String == as getKeyName(key) will return key if no custom mapping is defined)
188 | String data = s.getData().get(key) == null ? null : new String(s.getData().get(key), StandardCharsets.UTF_8);
189 | return requireNonNull(data, exceptionMessage, null);
190 | }
191 | String data = s.getData().get(mappedKey) == null ? null : new String(s.getData().get(mappedKey), StandardCharsets.UTF_8);
192 | return requireNonNull(data, exceptionMessage, mappedKey);
193 | }
194 |
195 | /**
196 | * Get optional data for the specified key (or the mapped key if key is mapped)
197 | *
198 | * @param s the Secret
199 | * @param key the key to get the data for (which may be mapped to another key).
200 | * @param exceptionMessage the detailMessage of the exception if the data for the key (or mapped key) was not
201 | * present.
202 | * @return Optional data for specified key
203 | * @throws CredentialsConversionException if the data was not present.
204 | */
205 | public static Optional getOptionalSecretData(V1Secret s, String key, String exceptionMessage) throws CredentialsConversionException {
206 | String mappedKey = getKeyName(s, key);
207 | if (s.getData().containsKey(key) || s.getData().containsKey(mappedKey)) {
208 | return Optional.of(getNonNullSecretData(s, key, exceptionMessage));
209 | }
210 | return Optional.empty();
211 | }
212 |
213 | /**
214 | * Get the mapping for the specified key name. Secrets can override the defaults used by the plugin by specifying an
215 | * attribute of the type {@code jenkins.io/credentials-keybinding-name} containing the custom name - for example
216 | * {@code jenkins.io/credentials-keybinding-foo=wibble}.
217 | *
218 | * @param s the secret to inspect for a custom name.
219 | * @param key the name of the key we are looking for.
220 | * @return the custom mapping for the key or {@code key} (identical object) if there is no custom mapping.
221 | */
222 | public static String getKeyName(V1Secret s, String key) {
223 | Map annotations = s.getMetadata().getAnnotations();
224 | if (annotations != null){
225 | final String annotationName = JENKINS_IO_CREDENTIALS_KEYBINDING_ANNOTATION_PREFIX + key;
226 | String customMapping = annotations.get(annotationName);
227 | if (customMapping == null) {
228 | // no entry
229 | return key;
230 | }
231 | if (customMapping.isEmpty()){
232 | LOG.log(Level.WARNING, "Secret {0} contains a mapping annotation {1} but has no entry - mapping will "
233 | + "not be performed",
234 | new Object[] {s.getMetadata().getName(), annotationName});
235 | return key;
236 | }
237 | return customMapping;
238 | }
239 | return key;
240 | }
241 | }
242 |
--------------------------------------------------------------------------------
/src/main/java/io/alauda/jenkins/plugins/credentials/KubernetesCredentialsProvider.java:
--------------------------------------------------------------------------------
1 | package io.alauda.jenkins.plugins.credentials;
2 |
3 | import com.cloudbees.plugins.credentials.Credentials;
4 | import com.cloudbees.plugins.credentials.CredentialsProvider;
5 | import com.cloudbees.plugins.credentials.CredentialsStore;
6 | import com.cloudbees.plugins.credentials.common.IdCredentials;
7 | import hudson.Extension;
8 | import hudson.ExtensionList;
9 | import hudson.model.ItemGroup;
10 | import hudson.model.ModelObject;
11 | import hudson.security.ACL;
12 | import io.alauda.jenkins.devops.support.KubernetesCluster;
13 | import io.alauda.jenkins.devops.support.KubernetesClusterConfigurationListener;
14 | import io.alauda.jenkins.plugins.credentials.convertor.CredentialsConversionException;
15 | import io.alauda.jenkins.plugins.credentials.convertor.SecretToCredentialConverter;
16 | import io.alauda.jenkins.plugins.credentials.metadata.CredentialsWithMetadata;
17 | import io.alauda.jenkins.plugins.credentials.metadata.MetadataProvider;
18 | import io.alauda.jenkins.plugins.credentials.rule.KubernetesSecretRule;
19 | import io.alauda.jenkins.plugins.credentials.scope.JenkinsRootScope;
20 | import io.alauda.jenkins.plugins.credentials.scope.KubernetesSecretScope;
21 | import io.kubernetes.client.ApiClient;
22 | import io.kubernetes.client.apis.CoreV1Api;
23 | import io.kubernetes.client.extended.controller.Controller;
24 | import io.kubernetes.client.extended.controller.ControllerManager;
25 | import io.kubernetes.client.extended.controller.builder.ControllerBuilder;
26 | import io.kubernetes.client.extended.controller.builder.ControllerManagerBuilder;
27 | import io.kubernetes.client.extended.controller.reconciler.Reconciler;
28 | import io.kubernetes.client.extended.controller.reconciler.Request;
29 | import io.kubernetes.client.extended.controller.reconciler.Result;
30 | import io.kubernetes.client.informer.SharedIndexInformer;
31 | import io.kubernetes.client.informer.SharedInformerFactory;
32 | import io.kubernetes.client.informer.cache.Lister;
33 | import io.kubernetes.client.models.V1ObjectMeta;
34 | import io.kubernetes.client.models.V1Secret;
35 | import io.kubernetes.client.models.V1SecretList;
36 | import jenkins.model.Jenkins;
37 | import org.acegisecurity.Authentication;
38 | import org.slf4j.Logger;
39 | import org.slf4j.LoggerFactory;
40 |
41 | import javax.annotation.Nonnull;
42 | import java.util.*;
43 | import java.util.concurrent.ConcurrentHashMap;
44 | import java.util.concurrent.ExecutorService;
45 | import java.util.concurrent.Executors;
46 |
47 | @Extension
48 | public class KubernetesCredentialsProvider extends CredentialsProvider implements KubernetesClusterConfigurationListener {
49 |
50 | private static final Logger logger = LoggerFactory.getLogger(KubernetesCredentialsProvider.class);
51 | private static final String CONTROLLER_NAME = "SecretController";
52 |
53 | // Maps of credentials keyed by credentials ID
54 | private ConcurrentHashMap credentials = new ConcurrentHashMap<>();
55 | private ControllerManager controllerManager;
56 | private ExecutorService controllerManagerThread;
57 |
58 |
59 | @Override
60 | public void onConfigChange(KubernetesCluster cluster, ApiClient client) {
61 | shutDown(null);
62 |
63 | SharedInformerFactory factory = new SharedInformerFactory();
64 | ControllerManagerBuilder manangerBuilder = ControllerBuilder
65 | .controllerManagerBuilder(factory);
66 |
67 | String labelSelector = KubernetesCredentialsProviderConfiguration.get().getLabelSelector();
68 |
69 | CoreV1Api coreV1Api = new CoreV1Api();
70 |
71 | SharedIndexInformer secretInformer = factory.sharedIndexInformerFor(
72 | callGeneratorParams -> coreV1Api.listSecretForAllNamespacesCall(
73 | null,
74 | null,
75 | labelSelector,
76 | null,
77 | null,
78 | callGeneratorParams.resourceVersion,
79 | callGeneratorParams.timeoutSeconds,
80 | callGeneratorParams.watch,
81 | null,
82 | null), V1Secret.class, V1SecretList.class);
83 |
84 |
85 | Controller controller = ControllerBuilder.defaultBuilder(factory).watch(
86 | (workQueue) ->
87 | ControllerBuilder.controllerWatchBuilder(V1Secret.class, workQueue)
88 | .withWorkQueueKeyFunc(secret ->
89 | new Request(secret.getMetadata().getNamespace(), secret.getMetadata().getName()))
90 | .withOnAddFilter(secret -> {
91 | logger.debug("[{}] receives event: Add; Secret '{}/{}'",
92 | CONTROLLER_NAME,
93 | secret.getMetadata().getNamespace(), secret.getMetadata().getName());
94 | return true;
95 | })
96 | .withOnUpdateFilter((oldSecret, newSecret) -> {
97 | String namespace = oldSecret.getMetadata().getNamespace();
98 | String name = oldSecret.getMetadata().getName();
99 |
100 | logger.debug("[{}] receives event: Update; Secret '{}/{}'",
101 | CONTROLLER_NAME,
102 | namespace, name);
103 |
104 | return true;
105 | })
106 | .withOnDeleteFilter((secret, aBoolean) -> {
107 | logger.debug("[{}] receives event: Add; Secret '{}/{}'",
108 | CONTROLLER_NAME,
109 | secret.getMetadata().getNamespace(), secret.getMetadata().getName());
110 | return true;
111 | }).build())
112 | .withReconciler(new SecretReconciler(new Lister<>(secretInformer.getIndexer())))
113 | .withName(CONTROLLER_NAME)
114 | .withWorkerCount(4)
115 | .build();
116 |
117 | controllerManager = manangerBuilder.addController(controller).build();
118 |
119 | controllerManagerThread = Executors.newSingleThreadExecutor();
120 | controllerManagerThread.submit(() -> controllerManager.run());
121 | }
122 |
123 | @Override
124 | public void onConfigError(KubernetesCluster cluster, Throwable reason) {
125 | shutDown(reason);
126 | }
127 |
128 | private void shutDown(Throwable reason) {
129 | if (controllerManager != null) {
130 | controllerManager.shutdown();
131 | controllerManager = null;
132 | }
133 |
134 | if (controllerManagerThread != null && !controllerManagerThread.isShutdown()) {
135 | controllerManagerThread.shutdown();
136 | }
137 |
138 | if (reason != null) {
139 | logger.error("Alauda DevOps Credentials Provider is stopped, reason {}", reason);
140 | } else {
141 | logger.error("Alauda DevOps Credentials Provider is stopped, reason is null, might be stopped by user");
142 | }
143 | }
144 |
145 |
146 | class SecretReconciler implements Reconciler {
147 |
148 | private Lister secretLister;
149 |
150 | public SecretReconciler(Lister secretLister) {
151 | this.secretLister = secretLister;
152 | }
153 |
154 | @Override
155 | public Result reconcile(Request request) {
156 | String namespace = request.getNamespace();
157 | String name = request.getName();
158 |
159 | V1Secret secret = secretLister.namespace(namespace).get(name);
160 | if (secret == null) {
161 | logger.debug("[{}] Unable to get Secret '{}/{}' from local list, will remove it", getControllerName(), namespace, name);
162 | String credId = SecretUtils.getCredentialId(new V1ObjectMeta().namespace(namespace).name(name));
163 | if (credentials.containsKey(credId)) {
164 | logger.debug("Secret Deleted - {}", credId);
165 | credentials.remove(credId);
166 | }
167 | return new Result(false);
168 | }
169 |
170 | IdCredentials cred = convertSecret(secret);
171 | if (cred != null) {
172 | logger.debug("Secret Added - {}", cred.getId());
173 | CredentialsWithMetadata credWithMetadata = addMetadataToCredentials(secret, cred);
174 | credentials.put(cred.getId(), credWithMetadata);
175 | return new Result(false);
176 | }
177 |
178 | return new Result(false);
179 | }
180 |
181 | public String getControllerName() {
182 | return CONTROLLER_NAME;
183 | }
184 | }
185 |
186 |
187 | @Nonnull
188 | @Override
189 | public List getCredentials(@Nonnull Class type, final ItemGroup itemGroup, Authentication authentication) {
190 | logger.debug("getCredentials called with type {} and authentication {}", type.getName(), authentication);
191 | if (ACL.SYSTEM.equals(authentication)) {
192 | List credentialsWithinScopes = getCredentialsWithinScope(type, itemGroup, authentication);
193 |
194 | List scopes = KubernetesSecretScope.matchedScopes(itemGroup);
195 | if (scopes.stream().anyMatch(s -> s.getClass().equals(JenkinsRootScope.class))) {
196 | return credentialsWithinScopes;
197 | }
198 |
199 |
200 | JenkinsRootScope rootScope = ExtensionList.lookup(JenkinsRootScope.class).get(0);
201 | credentials.forEach((s, credentialsWithMetadata) -> {
202 | if (rootScope.shouldShowInScope(Jenkins.getInstance(), credentialsWithMetadata)
203 | && type.isAssignableFrom(credentialsWithMetadata.getCredentials().getClass())) {
204 | C c = type.cast(credentialsWithMetadata.getCredentials());
205 | if (!credentialsWithinScopes.contains(c)) {
206 | credentialsWithinScopes.add(c);
207 | }
208 | }
209 |
210 | });
211 | return credentialsWithinScopes;
212 | }
213 | return Collections.emptyList();
214 | }
215 |
216 | public List getCredentialsWithinScope(@Nonnull Class type, final ItemGroup itemGroup, Authentication authentication) {
217 | logger.debug("getCredentials called with type {} and authentication {}", type.getName(), authentication);
218 | if (ACL.SYSTEM.equals(authentication)) {
219 | List list = new ArrayList<>();
220 | Set ids = new HashSet<>();
221 |
222 | List scopes = KubernetesSecretScope.matchedScopes(itemGroup);
223 |
224 | credentials.forEach((id, credentialsWithMetadata) -> {
225 | if (scopes.stream().anyMatch(scope -> scope.shouldShowInScope(itemGroup, credentialsWithMetadata))
226 | && type.isAssignableFrom(credentialsWithMetadata.getCredentials().getClass()) && ids.add(id)) {
227 | list.add(type.cast(credentialsWithMetadata.getCredentials()));
228 | }
229 | });
230 |
231 | return list;
232 | }
233 | return Collections.emptyList();
234 | }
235 |
236 |
237 | private CredentialsWithMetadata addMetadataToCredentials(V1Secret s, IdCredentials cred) {
238 | CredentialsWithMetadata credWithMetadata = new CredentialsWithMetadata<>(cred);
239 |
240 | MetadataProvider.all().forEach(metadataProvider -> metadataProvider.attach(s, credWithMetadata));
241 |
242 | return credWithMetadata;
243 | }
244 |
245 |
246 | private IdCredentials convertSecret(V1Secret s) {
247 | if (KubernetesSecretRule.shouldExclude(s)) {
248 | return null;
249 | }
250 |
251 | String type = getSecretType(s);
252 | SecretToCredentialConverter lookup = SecretToCredentialConverter.lookup(type);
253 | if (lookup != null) {
254 | try {
255 | return lookup.convert(s);
256 | } catch (CredentialsConversionException ex) {
257 | // do not spam the logs with the stacktrace...
258 | logger.debug("Failed to convert Secret '" + SecretUtils.getCredentialId(s) + "' of type " + type, ex);
259 | return null;
260 | }
261 | }
262 | return null;
263 | }
264 |
265 | private String getSecretType(V1Secret s) {
266 | return s.getType();
267 | }
268 |
269 | @Override
270 | public CredentialsStore getStore(ModelObject object) {
271 | if (!(object instanceof ItemGroup)) {
272 | return null;
273 | }
274 |
275 | ItemGroup owner = (ItemGroup) object;
276 | if (!KubernetesSecretScope.hasMatchedScope(owner)) {
277 | return null;
278 | }
279 |
280 | return new AlaudaKubernetesCredentialsStore(this, owner);
281 | }
282 |
283 | @Override
284 | @Nonnull
285 | public String getDisplayName() {
286 | return "Alauda DevOps Credentials Provider";
287 | }
288 |
289 | @Override
290 | public String getIconClassName() {
291 | return "icon-credentials-alauda-store";
292 | }
293 | }
294 |
--------------------------------------------------------------------------------