Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# for dev purposes only
FROM kestra/kestra:latest

# COPY build/libs/* /app/plugins/ # this is already handled in docker-compose.yml
COPY build/libs/* /app/plugins/
13 changes: 7 additions & 6 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ java {
}

group = "io.kestra.plugin"
description = 'Plugin template for Kestra'
description = 'GitLab plugin for Kestra'

tasks.withType(JavaCompile).configureEach {
options.encoding = "UTF-8"
Expand Down Expand Up @@ -96,6 +96,7 @@ dependencies {
testImplementation "org.junit.jupiter:junit-jupiter-engine"
testImplementation "org.hamcrest:hamcrest"
testImplementation "org.hamcrest:hamcrest-library"
testImplementation "org.wiremock:wiremock-standalone:3.5.2"
}

/**********************************************************************************************************************\
Expand Down Expand Up @@ -171,11 +172,11 @@ tasks.withType(GenerateModuleMetadata).configureEach {
jar {
manifest {
attributes(
"X-Kestra-Name": project.name,
"X-Kestra-Title": "Template",
"X-Kestra-Group": project.group + ".templates",
"X-Kestra-Description": project.description,
"X-Kestra-Version": project.version
"X-Kestra-Name": project.name,
"X-Kestra-Title": "GitLab",
"X-Kestra-Group": project.group + ".gitlab",
"X-Kestra-Description": project.description,
"X-Kestra-Version": project.version
)
}
}
Expand Down
2 changes: 1 addition & 1 deletion settings.gradle
Original file line number Diff line number Diff line change
@@ -1 +1 @@
rootProject.name = 'plugin-template'
rootProject.name = 'plugin-gitlab'
62 changes: 62 additions & 0 deletions src/main/java/io/kestra/plugin/gitlab/AbstractGitLabTask.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package io.kestra.plugin.gitlab;

import io.kestra.core.exceptions.IllegalVariableEvaluationException;
import io.kestra.core.http.HttpRequest;
import io.kestra.core.http.client.HttpClient;
import io.kestra.core.http.client.configurations.HttpConfiguration;
import io.kestra.core.models.property.Property;
import io.kestra.core.models.tasks.Task;
import io.kestra.core.runners.RunContext;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.*;
import lombok.experimental.SuperBuilder;

import java.net.URI;

@SuperBuilder
@ToString
@EqualsAndHashCode
@Getter
@NoArgsConstructor
public abstract class AbstractGitLabTask extends Task {

@Schema(title = "GitLab URL", description = "GitLab URL")
@Builder.Default
private Property<String> url = Property.ofValue("https://gitlab.com");

@Schema(title = "Personal Access Token", description = "GitLab Personal Access Token")
@NotNull
private Property<String> token;

@Schema(title = "Project ID", description = "GitLab project ID")
@NotNull
private Property<String> projectId;

@Schema(title = "API Path", description = "Custom API path for GitLab API endpoints")
@Builder.Default
private Property<String> apiPath = Property.ofValue("/api/v4/projects");

protected HttpClient httpClient(RunContext runContext) throws IllegalVariableEvaluationException {

HttpConfiguration config = null;
return new HttpClient(runContext, config);
}

protected HttpRequest.HttpRequestBuilder authenticatedRequestBuilder(String endpoint, RunContext runContext) throws IllegalVariableEvaluationException {
String baseUrl = runContext.render(this.url).as(String.class).orElse("https://gitlab.com");
String renderedToken = runContext.render(this.token).as(String.class).orElseThrow();
String fullUrl = baseUrl + endpoint;
return HttpRequest.builder()
.uri(URI.create(fullUrl))
.addHeader("PRIVATE-TOKEN", renderedToken)
.addHeader("Content-Type", "application/json");
}

protected String buildApiEndpoint(String resource, RunContext runContext) throws IllegalVariableEvaluationException {
String renderedApiPath = runContext.render(this.apiPath).as(String.class).orElse("/api/v4/projects");
String renderedProjectId = runContext.render(this.getProjectId()).as(String.class).orElseThrow();
return renderedApiPath + "/" + renderedProjectId + "/" + resource;
}

}
119 changes: 119 additions & 0 deletions src/main/java/io/kestra/plugin/gitlab/MergeRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package io.kestra.plugin.gitlab;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.kestra.core.http.HttpRequest;
import io.kestra.core.http.HttpResponse;
import io.kestra.core.http.client.HttpClient;
import io.kestra.core.models.annotations.Example;
import io.kestra.core.models.annotations.Plugin;
import io.kestra.core.models.property.Property;
import io.kestra.core.models.tasks.RunnableTask;
import io.kestra.core.runners.RunContext;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.*;
import lombok.experimental.SuperBuilder;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

@SuperBuilder
@ToString
@EqualsAndHashCode
@Getter
@NoArgsConstructor
@Schema(
title = "Create a GitLab merge request",
description = "Create a new merger request in a GitLab project"
)
@Plugin(examples = {
@Example(
title = "Create a merge request in a GitLab project using a project access token.",
full = true,
code = """
id: gitlab_merge_request
namespace: company.team

tasks:
- id: create_merge_request
type: io.kestra.plugin.gitlab.MergeRequest
url: https://gitlab.example.com
token: "{{ secret('GITLAB_TOKEN') }}"
projectId: "123"
title: "Feature: Add new functionality"
mergeRequestDescription: "This merge request adds new functionality to the project"
sourceBranch: "feat-testing"
targetBranch: "main"
"""
)
})
public class MergeRequest extends AbstractGitLabTask implements RunnableTask<MergeRequest.Output> {

@Schema(title = "Merge request title")
@NotNull
private Property<String> title;

@Schema(title = "Source branch")
@NotNull
private Property<String> sourceBranch;

@Schema(title = "Target branch")
@NotNull
private Property<String> targetBranch;

@Schema(title = "Merge request description")
private Property<String> mergeRequestDescription;

@Override
public Output run(RunContext runContext) throws Exception {
try (HttpClient client = httpClient(runContext)) {

Map<String, Object> body = new HashMap<>();

// Required fields for merge request creation
body.put("title", runContext.render(this.title).as(String.class).orElseThrow());

body.put("source_branch", runContext.render(this.sourceBranch).as(String.class).orElseThrow());

body.put("target_branch", runContext.render(this.targetBranch).as(String.class).orElseThrow());

// Optional fields
if (this.mergeRequestDescription != null) {
body.put("description", runContext.render(this.mergeRequestDescription).as(String.class).orElseThrow());
}

ObjectMapper mapper = new ObjectMapper();
String jsonBody = mapper.writeValueAsString(body);
String endpoint = buildApiEndpoint("merge_requests", runContext);

HttpRequest request = authenticatedRequestBuilder(endpoint, runContext)
.method("POST")
.body(new HttpRequest.StringRequestBody("application/json", StandardCharsets.UTF_8, jsonBody))
.build();

HttpResponse<Map> response = client.request(request, Map.class);
Map<String, Object> result = response.getBody();

return Output.builder()
.mergeReqID(result.get("id").toString())
.webUrl(result.get("web_url").toString())
.statusCode(response.getStatus().getCode())
.build();
}
}

@Builder
@Getter
public static class Output implements io.kestra.core.models.tasks.Output {
@Schema(title = "Created merge request ID")
private String mergeReqID;

@Schema(title = "web URL")
private String webUrl;

@Schema(title = "HTTP status code")
private Integer statusCode;
}

}
132 changes: 132 additions & 0 deletions src/main/java/io/kestra/plugin/gitlab/issues/Create.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package io.kestra.plugin.gitlab.issues;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.kestra.core.http.HttpRequest;
import io.kestra.core.http.HttpResponse;
import io.kestra.core.http.client.HttpClient;
import io.kestra.core.models.annotations.Example;
import io.kestra.core.models.annotations.Plugin;
import io.kestra.core.models.property.Property;
import io.kestra.core.models.tasks.RunnableTask;
import io.kestra.core.runners.RunContext;
import io.kestra.plugin.gitlab.AbstractGitLabTask;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.*;
import lombok.experimental.SuperBuilder;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@SuperBuilder
@ToString
@EqualsAndHashCode
@Getter
@NoArgsConstructor
@Schema(
title = "Create a GitLab issue",
description = "Create a new issue in a GitLab project"
)
@Plugin(examples = {
@Example(
title = "Create an issue in a GitLab project using a project access token.",
full = true,
code = """
id: gitlab_create_issue
namespace: company.team

tasks:
- id: create_issue
type: io.kestra.plugin.gitlab.issues.Create
url: https://gitlab.example.com
token: "{{ secret('GITLAB_TOKEN') }}"
projectId: "123"
title: "Bug report"
issueDescription: "Found a critical bug"
labels:
- bug
- critical
"""
),
@Example(
title = "Create an issue with custom API path for self-hosted GitLab.",
full = true,
code = """
id: gitlab_create_issue_custom
namespace: company.team

tasks:
- id: create_issue
type: io.kestra.plugin.gitlab.issues.Create
url: https://gitlab.example.com
apiPath: /api/v4/projects
token: "{{ secret('GITLAB_TOKEN') }}"
projectId: "123"
title: "Bug report"
issueDescription: "Found a critical bug"
"""
)
})
public class Create extends AbstractGitLabTask implements RunnableTask<Create.Output> {

@Schema(title = "Issue title")
@NotNull
private Property<String> title;

@Schema(title = "Issue description")
private Property<String> issueDescription;

@Schema(title = "Labels to assign to the issue")
private Property<List<String>> labels;

@Override
public Output run(RunContext runContext) throws Exception {
try (HttpClient client = httpClient(runContext)) {

Map<String, Object> body = new HashMap<>();
body.put("title", runContext.render(this.title).as(String.class).orElseThrow());
if (this.issueDescription != null) {
body.put("description", runContext.render(this.issueDescription).as(String.class).orElseThrow());
}
if (this.labels != null) {
List<String> renderedLabels = runContext.render(this.labels).asList(String.class);
body.put("labels", renderedLabels);
}
ObjectMapper mapper = new ObjectMapper();
String jsonBody = mapper.writeValueAsString(body);
String endpoint = buildApiEndpoint("issues", runContext);

HttpRequest request = authenticatedRequestBuilder(endpoint, runContext)
.method("POST")
.body(new HttpRequest.StringRequestBody("application/json",
StandardCharsets.UTF_8,
jsonBody))
.build();

HttpResponse<Map> response = client.request(request, Map.class);

Map<String, Object> result = response.getBody();

return Output.builder()
.issueId(result.get("id").toString())
.webUrl(result.get("web_url").toString())
.statusCode(response.getStatus().getCode())
.build();
}
}

@Builder
@Getter
public static class Output implements io.kestra.core.models.tasks.Output {
@Schema(title = "Created issue ID")
private String issueId;

@Schema(title = "Issue web URL")
private String webUrl;

@Schema(title = "HTTP status code")
private Integer statusCode;
}
}
Loading
Loading