From c3512c0fa2ea88b75405b3f6f1e8e880c803a60a Mon Sep 17 00:00:00 2001 From: Felix Heim Date: Mon, 21 Feb 2022 13:09:18 +0100 Subject: [PATCH 1/8] gitea commit status as copy of gitlab --- .../commitPublisher/Constants.java | 21 +- .../gitea/GiteaBuildStatus.java | 51 +++ .../commitPublisher/gitea/GiteaPublisher.java | 323 ++++++++++++++++++ .../commitPublisher/gitea/GiteaSettings.java | 258 ++++++++++++++ .../gitea/data/GiteaAccessLevel.java | 24 ++ .../gitea/data/GiteaCommitStatus.java | 20 ++ .../gitea/data/GiteaPermissions.java | 25 ++ .../gitea/data/GiteaRepoInfo.java | 25 ++ .../gitea/data/GiteaUserInfo.java | 27 ++ .../META-INF/build-server-plugin-voter.xml | 1 + .../gitea/giteaSettings.jsp | 48 +++ kotlin-dsl/CommitStatusPublisher.xml | 15 + 12 files changed, 837 insertions(+), 1 deletion(-) create mode 100644 commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/GiteaBuildStatus.java create mode 100644 commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/GiteaPublisher.java create mode 100644 commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/GiteaSettings.java create mode 100644 commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/data/GiteaAccessLevel.java create mode 100644 commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/data/GiteaCommitStatus.java create mode 100644 commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/data/GiteaPermissions.java create mode 100644 commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/data/GiteaRepoInfo.java create mode 100644 commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/data/GiteaUserInfo.java create mode 100644 commit-status-publisher-server/src/main/resources/buildServerResources/gitea/giteaSettings.jsp diff --git a/commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/Constants.java b/commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/Constants.java index f110785e..3a390cd0 100644 --- a/commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/Constants.java +++ b/commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/Constants.java @@ -85,6 +85,10 @@ public class Constants { public static final int STATUSES_TO_LOAD_THRESHOLD_DEFAULT_VAL = 50; public static final String GITLAB_FEATURE_TOGGLE_MERGE_RESULTS = "commitStatusPubliser.gitlab.supportMergeResults"; + public static final String GITEA_PUBLISHER_ID = "giteaStatusPublisher"; + public static final String GITEA_API_URL = "giteaApiUrl"; + public static final String GITEA_TOKEN = "secure:giteaAccessToken"; + @NotNull public String getVcsRootIdParam() { return VCS_ROOT_ID_PARAM; @@ -219,4 +223,19 @@ public String getGitlabServer() { public String getGitlabToken() { return GITLAB_TOKEN; } -} \ No newline at end of file + + @NotNull + public String getGiteaPublisherId() { + return GITEA_PUBLISHER_ID; + } + + @NotNull + public String getGiteaServer() { + return GITEA_API_URL; + } + + @NotNull + public String getGiteaToken() { + return GITEA_TOKEN; + } +} diff --git a/commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/GiteaBuildStatus.java b/commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/GiteaBuildStatus.java new file mode 100644 index 00000000..e4709e5e --- /dev/null +++ b/commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/GiteaBuildStatus.java @@ -0,0 +1,51 @@ +/* + * Copyright 2000-2022 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.buildServer.commitPublisher.gitea; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +public enum GiteaBuildStatus { + PENDING("pending"), + RUNNING("running"), + SUCCESS("success"), + FAILED("failed"), + CANCELED("canceled"); + + private static final Map INDEX = Arrays.stream(values()).collect(Collectors.toMap(GiteaBuildStatus::getName, Function.identity())); + + private final String myName; + + GiteaBuildStatus(@NotNull String name) { + myName = name; + } + + @NotNull + public String getName() { + return myName; + } + + @Nullable + public static GiteaBuildStatus getByName(@NotNull String name) { + return INDEX.get(name); + } +} diff --git a/commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/GiteaPublisher.java b/commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/GiteaPublisher.java new file mode 100644 index 00000000..a2035bcd --- /dev/null +++ b/commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/GiteaPublisher.java @@ -0,0 +1,323 @@ +/* + * Copyright 2000-2022 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.buildServer.commitPublisher.gitea; + +import com.google.gson.Gson; +import jetbrains.buildServer.BuildType; +import jetbrains.buildServer.commitPublisher.*; +import jetbrains.buildServer.commitPublisher.gitea.GiteaBuildStatus; +import jetbrains.buildServer.commitPublisher.gitea.GiteaSettings; +import jetbrains.buildServer.commitPublisher.gitea.data.GiteaCommitStatus; +import jetbrains.buildServer.serverSide.*; +import jetbrains.buildServer.serverSide.impl.LogUtil; +import jetbrains.buildServer.util.StringUtil; +import jetbrains.buildServer.vcs.VcsRoot; +import jetbrains.buildServer.vcs.VcsRootInstance; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import static jetbrains.buildServer.commitPublisher.LoggerUtil.LOG; + +class GiteaPublisher extends HttpBasedCommitStatusPublisher { + + private static final String REFS_HEADS = "refs/heads/"; + private static final String REFS_TAGS = "refs/tags/"; + private final Gson myGson = new Gson(); + private static final GitRepositoryParser VCS_URL_PARSER = new GitRepositoryParser(); + + private final WebLinks myLinks; + + GiteaPublisher(@NotNull CommitStatusPublisherSettings settings, + @NotNull SBuildType buildType, @NotNull String buildFeatureId, + @NotNull WebLinks links, + @NotNull Map params, + @NotNull CommitStatusPublisherProblems problems) { + super(settings, buildType, buildFeatureId, params, problems); + myLinks = links; + } + + + @NotNull + @Override + public String getId() { + return Constants.GITEA_PUBLISHER_ID; + } + + + @NotNull + @Override + public String toString() { + return "Gitea"; + } + + @Override + public boolean buildQueued(@NotNull BuildPromotion buildPromotion, @NotNull BuildRevision revision, @NotNull AdditionalTaskInfo additionalTaskInfo) throws PublisherException { + publish(buildPromotion, revision, GiteaBuildStatus.PENDING, additionalTaskInfo); + return true; + } + + @Override + public boolean buildRemovedFromQueue(@NotNull BuildPromotion buildPromotion, @NotNull BuildRevision revision, @NotNull AdditionalTaskInfo additionalTaskInfo) throws PublisherException { + publish(buildPromotion, revision, GiteaBuildStatus.CANCELED, additionalTaskInfo); + return true; + } + + @Override + public boolean buildStarted(@NotNull SBuild build, @NotNull BuildRevision revision) throws PublisherException { + publish(build, revision, GiteaBuildStatus.RUNNING, DefaultStatusMessages.BUILD_STARTED); + return true; + } + + + @Override + public boolean buildFinished(@NotNull SBuild build, @NotNull BuildRevision revision) throws PublisherException { + GiteaBuildStatus status = build.getBuildStatus().isSuccessful() ? GiteaBuildStatus.SUCCESS : GiteaBuildStatus.FAILED; + publish(build, revision, status, build.getStatusDescriptor().getText()); + return true; + } + + + @Override + public boolean buildFailureDetected(@NotNull SBuild build, @NotNull BuildRevision revision) throws PublisherException { + publish(build, revision, GiteaBuildStatus.FAILED, build.getStatusDescriptor().getText()); + return true; + } + + @Override + public boolean buildMarkedAsSuccessful(@NotNull SBuild build, @NotNull BuildRevision revision, boolean buildInProgress) throws PublisherException { + publish(build, revision, buildInProgress ? GiteaBuildStatus.RUNNING : GiteaBuildStatus.SUCCESS, DefaultStatusMessages.BUILD_MARKED_SUCCESSFULL); + return true; + } + + + @Override + public boolean buildInterrupted(@NotNull SBuild build, @NotNull BuildRevision revision) throws PublisherException { + publish(build, revision, GiteaBuildStatus.CANCELED, build.getStatusDescriptor().getText()); + return true; + } + + @Override + public RevisionStatus getRevisionStatusForRemovedBuild(@NotNull SQueuedBuild removedBuild, @NotNull BuildRevision revision) throws PublisherException { + GiteaCommitStatus commitStatus = getLatestCommitStatusForBuild(revision, removedBuild.getBuildType()); + return getRevisionStatusForRemovedBuild(removedBuild, commitStatus); + } + + RevisionStatus getRevisionStatusForRemovedBuild(@NotNull SQueuedBuild removedBuild, @Nullable GiteaCommitStatus commitStatus) { + if(commitStatus == null) { + return null; + } + Event event = getTriggeredEvent(commitStatus); + boolean isSameBuild = StringUtil.areEqual(myLinks.getQueuedBuildUrl(removedBuild), commitStatus.target_url); + return new RevisionStatus(event, commitStatus.description, isSameBuild); + } + + @Override + public RevisionStatus getRevisionStatus(@NotNull BuildPromotion buildPromotion, @NotNull BuildRevision revision) throws PublisherException { + GiteaCommitStatus commitStatus = getLatestCommitStatusForBuild(revision, buildPromotion.getBuildType()); + return getRevisionStatus(buildPromotion, commitStatus); + } + + private GiteaCommitStatus getLatestCommitStatusForBuild(@NotNull BuildRevision revision, @Nullable SBuildType buildType) throws PublisherException { + String url = buildRevisionStatusesUrl(revision, buildType); + ResponseEntityProcessor processor = new ResponseEntityProcessor<>(GiteaCommitStatus[].class); + GiteaCommitStatus[] commitStatuses = get(url, null, null, Collections.singletonMap("PRIVATE-TOKEN", getPrivateToken()), processor); + if (commitStatuses == null || commitStatuses.length == 0) { + return null; + } + return commitStatuses[0]; + } + + @Nullable + RevisionStatus getRevisionStatus(@NotNull BuildPromotion buildPromotion, @Nullable GiteaCommitStatus commitStatus) { + if(commitStatus == null) { + return null; + } + Event event = getTriggeredEvent(commitStatus); + boolean isSameBuild = StringUtil.areEqual(getViewUrl(buildPromotion), commitStatus.target_url); + return new RevisionStatus(event, commitStatus.description, isSameBuild); + } + + private String buildRevisionStatusesUrl(@NotNull BuildRevision revision, @Nullable BuildType buildType) throws PublisherException { + VcsRootInstance root = revision.getRoot(); + String apiUrl = getApiUrl(); + if (null == apiUrl || apiUrl.length() == 0) + throw new PublisherException("Missing Gitea API URL parameter"); + String pathPrefix = GiteaSettings.getPathPrefix(apiUrl); + Repository repository = parseRepository(root, pathPrefix); + if (repository == null) + throw new PublisherException("Cannot parse repository URL from VCS root " + root.getName()); + String statusesUrl = GiteaSettings.getProjectsUrl(getApiUrl(), repository.owner(), repository.repositoryName()) + "/repository/commits/" + revision.getRevision() + "/statuses"; + if (buildType != null) { + statusesUrl += ("?" + encodeParameter("name", buildType.getName())); + } + return statusesUrl; + } + + private Event getTriggeredEvent(GiteaCommitStatus commitStatus) { + if (commitStatus.status == null) { + LOG.warn("No Gitea build status is provided. Related event can not be calculated"); + return null; + } + GiteaBuildStatus status = GiteaBuildStatus.getByName(commitStatus.status); + if (status == null) { + LOG.warn("Unknown Gitea build status \"" + commitStatus.status + "\". Related event can not be calculated"); + return null; + } + + switch (status) { + case CANCELED: + if (commitStatus.description != null && commitStatus.description.contains(DefaultStatusMessages.BUILD_REMOVED_FROM_QUEUE)) { + return Event.REMOVED_FROM_QUEUE; + } + return null; + case PENDING: + return Event.QUEUED; + case RUNNING: + case SUCCESS: + case FAILED: + return null; + default: + LOG.warn("No event is assosiated with Gitea build status \"" + status + "\". Related event can not be defined"); + } + return null; + } + + + private void publish(@NotNull SBuild build, + @NotNull BuildRevision revision, + @NotNull GiteaBuildStatus status, + @NotNull String description) throws PublisherException { + String message = createMessage(status, build.getBuildTypeName(), revision, myLinks.getViewResultsUrl(build), description); + publish(message, revision, LogUtil.describe(build)); + } + + private void publish(@NotNull BuildPromotion buildPromotion, + @NotNull BuildRevision revision, + @NotNull GiteaBuildStatus status, + @NotNull AdditionalTaskInfo additionalTaskInfo) throws PublisherException { + String url = getViewUrl(buildPromotion); + String description = additionalTaskInfo.compileQueueRelatedMessage(); + String message = createMessage(status, buildPromotion.getBuildType().getName(), revision, url, description); + publish(message, revision, LogUtil.describe(buildPromotion)); + } + + private String getViewUrl(BuildPromotion buildPromotion) { + SBuild build = buildPromotion.getAssociatedBuild(); + if (build != null) { + return myLinks.getViewResultsUrl(build); + } + SQueuedBuild queuedBuild = buildPromotion.getQueuedBuild(); + if (queuedBuild != null) { + return myLinks.getQueuedBuildUrl(queuedBuild); + } + return buildPromotion.getBuildType() != null ? myLinks.getConfigurationHomePageUrl(buildPromotion.getBuildType()) : + myLinks.getRootUrlByProjectExternalId(buildPromotion.getProjectExternalId()); + } + + private void publish(@NotNull String message, + @NotNull BuildRevision revision, + @NotNull String buildDescription) throws PublisherException { + VcsRootInstance root = revision.getRoot(); + String apiUrl = getApiUrl(); + if (null == apiUrl || apiUrl.length() == 0) + throw new PublisherException("Missing Gitea API URL parameter"); + String pathPrefix = GiteaSettings.getPathPrefix(apiUrl); + Repository repository = parseRepository(root, pathPrefix); + if (repository == null) + throw new PublisherException("Cannot parse repository URL from VCS root " + root.getName()); + + try { + publish(revision.getRevision(), message, repository, buildDescription); + } catch (Exception e) { + throw new PublisherException("Cannot publish status to Gitea for VCS root " + + revision.getRoot().getName() + ": " + e.toString(), e); + } + } + + private void publish(@NotNull String commit, @NotNull String data, @NotNull Repository repository, @NotNull String buildDescription) { + String url = GiteaSettings.getProjectsUrl(getApiUrl(), repository.owner(), repository.repositoryName()) + "/statuses/" + commit; + LOG.debug("Request url: " + url + ", message: " + data); + postJson(url, null, null, data, Collections.singletonMap("PRIVATE-TOKEN", getPrivateToken()), buildDescription); + } + + @Override + public void processResponse(HttpHelper.HttpResponse response) throws HttpPublisherException { + final int statusCode = response.getStatusCode(); + if (statusCode >= 400) { + String responseString = response.getContent(); + if (!responseString.contains("Cannot transition status via :enqueue from :pending") && + !responseString.contains("Cannot transition status via :enqueue from :running") && + !responseString.contains("Cannot transition status via :run from :running")) { + throw new HttpPublisherException(statusCode, + response.getStatusText(), "HTTP response error: " + responseString); + } + } + } + + @NotNull + private String createMessage(@NotNull GiteaBuildStatus status, + @NotNull String name, + @NotNull BuildRevision revision, + @NotNull String url, + @NotNull String description) { + + RepositoryVersion repositoryVersion = revision.getRepositoryVersion(); + String ref = repositoryVersion.getVcsBranch(); + if (ref != null) { + if (ref.startsWith(REFS_HEADS)) { + ref = ref.substring(REFS_HEADS.length()); + } else if (ref.startsWith(REFS_TAGS)) { + ref = ref.substring(REFS_TAGS.length()); + } else { + ref = null; + } + } + + final Map data = new LinkedHashMap(); + data.put("state", status.getName()); + data.put("name", name); + data.put("target_url", url); + data.put("description", description); + if (ref != null) + data.put("ref", ref); + return myGson.toJson(data); + } + + @Nullable + static Repository parseRepository(@NotNull VcsRoot root, @Nullable String pathPrefix) { + if ("jetbrains.git".equals(root.getVcsName())) { + String url = root.getProperty("url"); + return url == null ? null : VCS_URL_PARSER.parseRepositoryUrl(url, pathPrefix); + } else { + return null; + } + } + + + private String getApiUrl() { + return HttpHelper.stripTrailingSlash(myParams.get(Constants.GITEA_API_URL)); + } + + private String getPrivateToken() { + return myParams.get(Constants.GITEA_TOKEN); + } + +} diff --git a/commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/GiteaSettings.java b/commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/GiteaSettings.java new file mode 100644 index 00000000..bac15fa6 --- /dev/null +++ b/commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/GiteaSettings.java @@ -0,0 +1,258 @@ +/* + * Copyright 2000-2022 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.buildServer.commitPublisher.gitea; + +import jetbrains.buildServer.commitPublisher.*; +import jetbrains.buildServer.commitPublisher.CommitStatusPublisher.Event; +import jetbrains.buildServer.commitPublisher.gitea.GiteaPublisher; +import jetbrains.buildServer.commitPublisher.gitea.data.GiteaRepoInfo; +import jetbrains.buildServer.commitPublisher.gitea.data.GiteaUserInfo; +import jetbrains.buildServer.serverSide.*; +import jetbrains.buildServer.util.ssl.SSLTrustStoreProvider; +import jetbrains.buildServer.vcs.VcsRoot; +import jetbrains.buildServer.web.openapi.PluginDescriptor; +import jetbrains.buildServer.web.util.WebUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; +import java.util.regex.Pattern; + +public class GiteaSettings extends BasePublisherSettings implements CommitStatusPublisherSettings { + + private static final Pattern URL_WITH_API_SUFFIX = Pattern.compile("(.*)/api/v."); + + private static final Set mySupportedEvents = new HashSet() {{ + add(Event.STARTED); + add(Event.FINISHED); + add(Event.MARKED_AS_SUCCESSFUL); + add(Event.INTERRUPTED); + add(Event.FAILURE_DETECTED); + }}; + + private static final Set mySupportedEventsWithQueued = new HashSet() {{ + add(Event.QUEUED); + add(Event.REMOVED_FROM_QUEUE); + addAll(mySupportedEvents); + }}; + + public GiteaSettings(@NotNull PluginDescriptor descriptor, + @NotNull WebLinks links, + @NotNull CommitStatusPublisherProblems problems, + @NotNull SSLTrustStoreProvider trustStoreProvider) { + super(descriptor, links, problems, trustStoreProvider); + } + + @NotNull + @Override + public String getId() { + return Constants.GITEA_PUBLISHER_ID; + } + + @NotNull + @Override + public String getName() { + return "Gitea"; + } + + @Nullable + @Override + public String getEditSettingsUrl() { + return "gitea/giteaSettings.jsp"; + } + + @NotNull + @Override + public GiteaPublisher createPublisher(@NotNull SBuildType buildType, @NotNull String buildFeatureId, @NotNull Map params) { + return new GiteaPublisher(this, buildType, buildFeatureId, myLinks, params, myProblems); + } + + @Override + public boolean isTestConnectionSupported() { + return true; + } + + @Override + public void testConnection(@NotNull BuildTypeIdentity buildTypeOrTemplate, @NotNull VcsRoot root, @NotNull Map params) throws PublisherException { + String apiUrl = params.get(Constants.GITEA_API_URL); + if (null == apiUrl || apiUrl.length() == 0) + throw new PublisherException("Missing Gitea API URL parameter"); + String pathPrefix = getPathPrefix(apiUrl); + Repository repository = GiteaPublisher.parseRepository(root, pathPrefix); + if (null == repository) + throw new PublisherException("Cannot parse repository URL from VCS root " + root.getName()); + String token = params.get(Constants.GITEA_TOKEN); + if (null == token || token.length() == 0) + throw new PublisherException("Missing Gitea API access token"); + try { + IOGuard.allowNetworkCall(() -> { + ProjectInfoResponseProcessor processorPrj = new ProjectInfoResponseProcessor(); + HttpHelper.get(getProjectsUrl(apiUrl, repository.owner(), repository.repositoryName()), + null, null, Collections.singletonMap("PRIVATE-TOKEN", token), + BaseCommitStatusPublisher.DEFAULT_CONNECTION_TIMEOUT, trustStore(), processorPrj); + if (processorPrj.getAccessLevel() < 30) { + UserInfoResponseProcessor processorUser = new UserInfoResponseProcessor(); + HttpHelper.get(getUserUrl(apiUrl), null, null, Collections.singletonMap("PRIVATE-TOKEN", token), + BaseCommitStatusPublisher.DEFAULT_CONNECTION_TIMEOUT, trustStore(), processorUser); + if (!processorUser.isAdmin()) { + throw new HttpPublisherException("Gitea does not grant enough permissions to publish a commit status"); + } + } + }); + } catch (Exception ex) { + throw new PublisherException(String.format("Gitea publisher has failed to connect to \"%s\" repository", repository.url()), ex); + } + } + + @Nullable + public static String getPathPrefix(final String apiUrl) { + if (!URL_WITH_API_SUFFIX.matcher(apiUrl).matches()) return null; + try { + URI uri = new URI(apiUrl); + String path = uri.getPath(); + return path.substring(0, path.length() - "/api/v1".length()); + } catch (URISyntaxException e) { + return null; + } + } + + @NotNull + @Override + public String describeParameters(@NotNull Map params) { + String result = super.describeParameters(params); + String url = params.get(Constants.GITEA_API_URL); + if (url != null) + result += " " + WebUtil.escapeXml(url); + return result; + } + + @Nullable + @Override + public PropertiesProcessor getParametersProcessor() { + return new PropertiesProcessor() { + public Collection process(Map params) { + List errors = new ArrayList(); + if (params.get(Constants.GITEA_API_URL) == null) + errors.add(new InvalidProperty(Constants.GITEA_API_URL, "Gitea API URL must be specified")); + if (params.get(Constants.GITEA_TOKEN) == null) + errors.add(new InvalidProperty(Constants.GITEA_TOKEN, "Access token must be specified")); + return errors; + } + }; + } + + @NotNull + public static String getProjectsUrl(@NotNull String apiUrl, @NotNull String owner, @NotNull String repo) { + return apiUrl + "/projects/" + owner.replace(".", "%2E").replace("/", "%2F") + "%2F" + repo.replace(".", "%2E"); + } + + @NotNull + public static String getUserUrl(@NotNull String apiUrl) { + return apiUrl + "/user"; + } + + @Override + public boolean isPublishingForVcsRoot(final VcsRoot vcsRoot) { + return "jetbrains.git".equals(vcsRoot.getVcsName()); + } + + @Override + protected Set getSupportedEvents(final SBuildType buildType, final Map params) { + return isBuildQueuedSupported(buildType, params) ? mySupportedEventsWithQueued : mySupportedEvents; + } + + private abstract class JsonResponseProcessor extends DefaultHttpResponseProcessor { + + private final Class myInfoClass; + private T myInfo; + + JsonResponseProcessor(Class infoClass) { + myInfoClass = infoClass; + } + + T getInfo() { + return myInfo; + } + + @Override + public void processResponse(HttpHelper.HttpResponse response) throws HttpPublisherException, IOException { + + super.processResponse(response); + + final String json = response.getContent(); + if (null == json) { + throw new HttpPublisherException("Gitea publisher has received no response"); + } + myInfo = myGson.fromJson(json, myInfoClass); + } + } + + private class ProjectInfoResponseProcessor extends JsonResponseProcessor { + + private int myAccessLevel; + + ProjectInfoResponseProcessor() { + super(GiteaRepoInfo.class); + } + + int getAccessLevel() { + return myAccessLevel; + } + + @Override + public void processResponse(HttpHelper.HttpResponse response) throws HttpPublisherException, IOException { + myAccessLevel = 0; + super.processResponse(response); + GiteaRepoInfo repoInfo = getInfo(); + if (null == repoInfo || null == repoInfo.id || null == repoInfo.permissions) { + throw new HttpPublisherException("Gitea publisher has received a malformed response"); + } + if (null != repoInfo.permissions.project_access) + myAccessLevel = repoInfo.permissions.project_access.access_level; + if (null != repoInfo.permissions.group_access && myAccessLevel < repoInfo.permissions.group_access.access_level) + myAccessLevel = repoInfo.permissions.group_access.access_level; + } + } + + private class UserInfoResponseProcessor extends JsonResponseProcessor { + + private boolean myIsAdmin; + + UserInfoResponseProcessor() { + super(GiteaUserInfo.class); + } + + boolean isAdmin() { + return myIsAdmin; + } + + @Override + public void processResponse(HttpHelper.HttpResponse response) throws HttpPublisherException, IOException { + myIsAdmin = false; + super.processResponse(response); + GiteaUserInfo userInfo = getInfo(); + if (null == userInfo || null == userInfo.id) { + throw new HttpPublisherException("Gitea publisher has received a malformed response"); + } + myIsAdmin = userInfo.is_admin; + } + } + +} diff --git a/commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/data/GiteaAccessLevel.java b/commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/data/GiteaAccessLevel.java new file mode 100644 index 00000000..c3ec8084 --- /dev/null +++ b/commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/data/GiteaAccessLevel.java @@ -0,0 +1,24 @@ +/* + * Copyright 2000-2022 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.buildServer.commitPublisher.gitea.data; + +/** + * @author Felix Heim, 21/02/22. + */ +public class GiteaAccessLevel { + public int access_level; +} diff --git a/commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/data/GiteaCommitStatus.java b/commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/data/GiteaCommitStatus.java new file mode 100644 index 00000000..c20a2ca8 --- /dev/null +++ b/commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/data/GiteaCommitStatus.java @@ -0,0 +1,20 @@ +package jetbrains.buildServer.commitPublisher.gitea.data; + +/** + * @author Felix Heim, 21/02/22. + */ +public class GiteaCommitStatus { + public final Long id; + public final String status; + public final String description; + public final String name; + public final String target_url; + + public GiteaCommitStatus(Long id, String status, String description, String name, String target_url) { + this.id = id; + this.status = status; + this.description = description; + this.name = name; + this.target_url = target_url; + } +} diff --git a/commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/data/GiteaPermissions.java b/commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/data/GiteaPermissions.java new file mode 100644 index 00000000..70201139 --- /dev/null +++ b/commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/data/GiteaPermissions.java @@ -0,0 +1,25 @@ +/* + * Copyright 2000-2022 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.buildServer.commitPublisher.gitea.data; + +/** + * @author Felix Heim, 21/02/22. + */ +public class GiteaPermissions { + public GiteaAccessLevel project_access; + public GiteaAccessLevel group_access; +} diff --git a/commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/data/GiteaRepoInfo.java b/commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/data/GiteaRepoInfo.java new file mode 100644 index 00000000..0f4b1a44 --- /dev/null +++ b/commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/data/GiteaRepoInfo.java @@ -0,0 +1,25 @@ +/* + * Copyright 2000-2022 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.buildServer.commitPublisher.gitea.data; + +/** + * This class does not represent full repository information. + */ +public class GiteaRepoInfo { + public String id; + public GiteaPermissions permissions; +} diff --git a/commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/data/GiteaUserInfo.java b/commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/data/GiteaUserInfo.java new file mode 100644 index 00000000..d567f61f --- /dev/null +++ b/commit-status-publisher-server/src/main/java/jetbrains/buildServer/commitPublisher/gitea/data/GiteaUserInfo.java @@ -0,0 +1,27 @@ +/* + * Copyright 2000-2022 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.buildServer.commitPublisher.gitea.data; + +/** + * @author Felix Heim, 21/02/22. + */ +public class GiteaUserInfo { + public String id; + public String name; + public String username; + public boolean is_admin; +} diff --git a/commit-status-publisher-server/src/main/resources/META-INF/build-server-plugin-voter.xml b/commit-status-publisher-server/src/main/resources/META-INF/build-server-plugin-voter.xml index a710cbf6..0a24ee86 100644 --- a/commit-status-publisher-server/src/main/resources/META-INF/build-server-plugin-voter.xml +++ b/commit-status-publisher-server/src/main/resources/META-INF/build-server-plugin-voter.xml @@ -38,6 +38,7 @@ + diff --git a/commit-status-publisher-server/src/main/resources/buildServerResources/gitea/giteaSettings.jsp b/commit-status-publisher-server/src/main/resources/buildServerResources/gitea/giteaSettings.jsp new file mode 100644 index 00000000..26a4e4d4 --- /dev/null +++ b/commit-status-publisher-server/src/main/resources/buildServerResources/gitea/giteaSettings.jsp @@ -0,0 +1,48 @@ +<%@ include file="/include-internal.jsp" %> +<%@ taglib prefix="props" tagdir="/WEB-INF/tags/props" %> +<%@ taglib prefix="l" tagdir="/WEB-INF/tags/layout" %> +<%-- + ~ Copyright 2000-2022 JetBrains s.r.o. + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --%> + + + + + + + + Format: http[s]://<hostname>[:<port>]/api/v1 + + + + + + + + + + + Can be found at /user/settings/applications in Gitea + + + + + + + \ No newline at end of file diff --git a/kotlin-dsl/CommitStatusPublisher.xml b/kotlin-dsl/CommitStatusPublisher.xml index 204d7226..8cebe4b0 100644 --- a/kotlin-dsl/CommitStatusPublisher.xml +++ b/kotlin-dsl/CommitStatusPublisher.xml @@ -176,6 +176,21 @@ +