Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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'
66 changes: 66 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,66 @@
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.of( "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.of("/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).orElseThrow();
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).orElseThrow();
String renderedProjectId = runContext.render(this.getProjectId()).as(String.class).orElseThrow();
return renderedApiPath + "/" + renderedProjectId + "/" + resource;
}

}
128 changes: 128 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,128 @@
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.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
if(this.title != null) {
body.put("title", runContext.render(this.title).as(String.class).orElseThrow());
}

if(this.sourceBranch != null) {
body.put("source_branch", runContext.render(this.sourceBranch).as(String.class).orElseThrow());
}

if(this.targetBranch != null) {
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;
}

}
135 changes: 135 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,135 @@
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.CreateIssue
url: https://gitlab.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.CreateIssue
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