diff --git a/docs/README.md b/docs/README.md index 024f6040..bf058867 100644 --- a/docs/README.md +++ b/docs/README.md @@ -40,7 +40,7 @@ ### Configure the item -1. When configuring the new item, select "Repository Sources" +1. When configuring the new item, select "Repository Sources" ℹ️ **This is only necessary when using `branch-api` plugin version >=2.7.0** @@ -48,3 +48,62 @@ 3. Add the access token created before for the jenkins user in Gitea. Ignore the error about the token not having the correct length. 4. In the "Owner" field, add the name of the organization in Gitea you want to build projects for (**not** the full name). 5. Fill the rest of the form as required. Click "Save". The following scan should list the repositories that the jenkins user can see in the organization selected. + +## Using a Gitea personal access token in a Jenkins Pipeline + +["Using credentials"](https://www.jenkins.io/doc/book/using/using-credentials/) includes a Jenkins Pipeline credentials tutorial. +Additional credentials examples are available in the ["Injecting secrets into builds"](https://docs.cloudbees.com/docs/cloudbees-ci/latest/secure/injecting-secrets) page from CloudBees. + +The Gitea plugin includes support for Gitea personal access tokens as Jenkins [credentials](https://www.jenkins.io/doc/book/using/using-credentials/). +Personal access tokens are used to authenticate Gitea repository and API access without using a Gitea username and password. +Gitea personal access tokens are defined in Gitea from the "Applications" page of each Gitea user's "Settings" (/user/settings/applications). +Personal access token permissions can be defined to grant access to a subset of the resources of the Gitea server. + +Once a Gitea personal access token has been created in Gitea and added to Jenkins as a credential, it can be referenced from a Pipeline job using the `withCredentials` Pipeline step. +Use the [Pipeline syntax snippet generator](https://www.jenkins.io/pipeline/getting-started-pipelines/#using-snippet-generator) to create an example of the `withCredentials` step. +Choose "Secret text" as the credential type in the snippet generator. + +A typical example of a Gitea personal access token in a Jenkins declarative Pipeline would look like: + +```groovy +pipeline { + agent any + stages { + stage('Checkout') { + steps { + withCredentials([string(credentialsId: 'my-gitea-token', variable: 'MY_TOKEN')]) { + if (isUnix()) { + sh 'git clone https://$MY_TOKEN@gitea.com/exampleUser/private-repo.git' + } else { + bat 'git clone https://%MY_TOKEN%@gitea.com/exampleUser/private-repo.git' + } + } + } + } + } +} +``` + +A typical example of a Gitea personal access token in a Jenkins scripted Pipeline would look like: + +```groovy +node { + stage('Checkout') { + withCredentials([string(credentialsId: 'my-gitea-token', variable: 'MY_TOKEN')]) { + if (isUnix()) { + sh 'git clone https://$MY_TOKEN@gitea.com/exampleUser/private-repo.git' + } else { + bat 'git clone https://%MY_TOKEN%@gitea.com/exampleUser/private-repo.git' + } + } + } +} +``` + +### Note + +You should use a single quote (`'`) instead of a double quote (`"`) whenever you can. +This is particularly important in Pipelines where a statement may be interpreted by both the Pipeline engine and an external interpreter, such as a Unix shell (`sh`) or Windows Command (`bat`) or Powershell (`ps`). +This reduces complications with password masking and command processing. +The `sh` step in the above examples properly demonstrates this. +It references an environment variable, so the single-quoted string passes its value unprocessed to the `sh` step, and the shell interprets `$MY_TOKEN`. diff --git a/pom.xml b/pom.xml index 4067419b..57c2c8f7 100644 --- a/pom.xml +++ b/pom.xml @@ -92,6 +92,10 @@ org.jenkins-ci.plugins display-url-api + + org.jenkins-ci.plugins + plain-credentials + org.jenkins-ci.plugins @@ -109,6 +113,11 @@ workflow-multibranch test + + org.jenkins-ci.plugins.workflow + workflow-basic-steps + test + diff --git a/src/main/java/org/jenkinsci/plugin/gitea/credentials/PersonalAccessTokenImpl.java b/src/main/java/org/jenkinsci/plugin/gitea/credentials/PersonalAccessTokenImpl.java index a6b730ff..dc0f1267 100644 --- a/src/main/java/org/jenkinsci/plugin/gitea/credentials/PersonalAccessTokenImpl.java +++ b/src/main/java/org/jenkinsci/plugin/gitea/credentials/PersonalAccessTokenImpl.java @@ -36,6 +36,7 @@ import jenkins.model.Jenkins; import org.apache.commons.lang.StringUtils; import org.jenkinsci.Symbol; +import org.jenkinsci.plugins.plaincredentials.StringCredentials; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.stapler.DataBoundConstructor; @@ -45,7 +46,7 @@ * Default implementation of {@link PersonalAccessToken} for use by {@link Jenkins} {@link CredentialsProvider} * instances that store {@link Secret} locally. */ -public class PersonalAccessTokenImpl extends BaseStandardCredentials implements StandardUsernameCredentials, PersonalAccessToken { +public class PersonalAccessTokenImpl extends BaseStandardCredentials implements StandardUsernameCredentials, PersonalAccessToken, StringCredentials { /** * Our token. */ @@ -94,6 +95,12 @@ public Secret getPassword() { return getToken(); } + @NonNull + @Override + public Secret getSecret() { + return getToken(); + } + /** * Our descriptor. */ diff --git a/src/test/java/org/jenkinsci/plugin/gitea/credentials/PersonalAccessTokenBindingTest.java b/src/test/java/org/jenkinsci/plugin/gitea/credentials/PersonalAccessTokenBindingTest.java new file mode 100644 index 00000000..87b7d183 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugin/gitea/credentials/PersonalAccessTokenBindingTest.java @@ -0,0 +1,59 @@ +package org.jenkinsci.plugin.gitea.credentials; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +import org.apache.commons.io.IOUtils; +import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; +import org.jenkinsci.plugins.workflow.job.WorkflowJob; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.junit.jupiter.WithJenkins; + +import com.cloudbees.plugins.credentials.CredentialsProvider; +import com.cloudbees.plugins.credentials.CredentialsScope; +import com.cloudbees.plugins.credentials.CredentialsStore; +import com.cloudbees.plugins.credentials.SystemCredentialsProvider; +import com.cloudbees.plugins.credentials.domains.Domain; + +import hudson.model.Run; + +@WithJenkins +class PersonalAccessTokenBindingTest { + + private static final String API_TOKEN = "secret"; + private static final String API_TOKEN_ID = "personalAccessTokenId"; + + private JenkinsRule jenkins; + + @BeforeEach + void setUp(JenkinsRule rule) throws Exception { + jenkins = rule; + for (CredentialsStore credentialsStore : CredentialsProvider.lookupStores(jenkins.jenkins)) { + if (credentialsStore instanceof SystemCredentialsProvider.StoreImpl) { + List domains = credentialsStore.getDomains(); + credentialsStore.addCredentials( + domains.get(0), + new PersonalAccessTokenImpl( + CredentialsScope.GLOBAL, + API_TOKEN_ID, + "Gitea Personal Access Token", + API_TOKEN)); + } + } + } + + @Test + void withCredentials_success() throws Exception { + WorkflowJob project = jenkins.createProject(WorkflowJob.class); + String pipelineText = IOUtils.toString( + getClass().getResourceAsStream("pipeline/withCredentials-pipeline.groovy"), StandardCharsets.UTF_8); + project.setDefinition(new CpsFlowDefinition(pipelineText, false)); + Run build = jenkins.buildAndAssertSuccess(project); + // Pipeline script outputs a substring of credential so that it will not be masked + jenkins.assertLogContains("Token1 is ecret", build); + // Pipeline script shell and bat masks credential + jenkins.assertLogContains("API_TOKEN1 is ****", build); + } +} diff --git a/src/test/resources/org/jenkinsci/plugin/gitea/credentials/pipeline/withCredentials-pipeline.groovy b/src/test/resources/org/jenkinsci/plugin/gitea/credentials/pipeline/withCredentials-pipeline.groovy new file mode 100644 index 00000000..301954d6 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugin/gitea/credentials/pipeline/withCredentials-pipeline.groovy @@ -0,0 +1,13 @@ +node { + withCredentials([string( + credentialsId: 'personalAccessTokenId', + variable: 'API_TOKEN1' + )]) { + echo "Token1 is ${API_TOKEN1.substring(1)}" + if (isUnix()) { + sh 'echo API_TOKEN1 is $API_TOKEN1' + } else { + bat 'echo API_TOKEN1 is %API_TOKEN1%' + } + } +}