Skip to content

Commit

Permalink
fix(tasks): Clone task properly outputs clone destination directory i…
Browse files Browse the repository at this point in the history
…nstead of .git directory + private repositories testing
  • Loading branch information
brian-mulier-p committed Dec 8, 2023
1 parent d51fd8c commit f7fa540
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 76 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ jobs:

# Gradle check
- name: Build with Gradle
run: ./gradlew check
run: |
echo "kestra.git.pat: ${{ secrets.GH_PERSONAL_TOKEN }}" > src/test/resources/application-test.yml
./gradlew check
# Publish
- name: Publish package to Sonatype
Expand Down
85 changes: 85 additions & 0 deletions src/main/java/io/kestra/plugin/git/AbstractGitTask.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package io.kestra.plugin.git;

import io.kestra.core.models.annotations.PluginProperty;
import io.kestra.core.models.tasks.Task;
import io.kestra.core.runners.RunContext;
import io.kestra.plugin.git.services.SshTransportConfigCallback;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.eclipse.jgit.api.TransportCommand;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Set;

@SuperBuilder(toBuilder = true)
@NoArgsConstructor
@Getter
public abstract class AbstractGitTask extends Task {
@Schema(
title = "The URI to clone from"
)
@PluginProperty(dynamic = true)
protected String url;

@Schema(
title = "The username or organization."
)
@PluginProperty(dynamic = true)
protected String username;

@Schema(
title = "The password or personal access token."
)
@PluginProperty(dynamic = true)
protected String password;

@Schema(
title = "The private keyfile content used to connect."
)
@PluginProperty(dynamic = true)
protected String privateKey;

@Schema(
title = "The passphrase for the `privateKey`."
)
@PluginProperty(dynamic = true)
protected String passphrase;


@Schema(
title = "The initial Git branch."
)
@PluginProperty(dynamic = true)
public abstract String getBranch();

protected <T extends TransportCommand> T authentified(T command, RunContext runContext) throws Exception {
if (this.username != null && this.password != null) {
command.setCredentialsProvider(new UsernamePasswordCredentialsProvider(
runContext.render(this.username),
runContext.render(this.password)
));
}

if (this.privateKey != null) {
Path privateKey = runContext.tempFile(
runContext.render(this.privateKey).getBytes(StandardCharsets.UTF_8),
null
);

Files.setPosixFilePermissions(privateKey, Set.of(PosixFilePermission.OWNER_READ));

command.setTransportConfigCallback(new SshTransportConfigCallback(
privateKey.toFile(),
runContext.render(this.passphrase)
));
}

return command;
}
}
91 changes: 18 additions & 73 deletions src/main/java/io/kestra/plugin/git/Clone.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,19 @@
import io.kestra.core.models.annotations.Plugin;
import io.kestra.core.models.annotations.PluginProperty;
import io.kestra.core.models.tasks.RunnableTask;
import io.kestra.core.models.tasks.Task;
import io.kestra.core.runners.RunContext;
import io.kestra.plugin.git.services.SshTransportConfigCallback;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import lombok.experimental.SuperBuilder;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.slf4j.Logger;

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Set;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.nio.file.Path;

@SuperBuilder
@SuperBuilder(toBuilder = true)
@ToString
@EqualsAndHashCode
@Getter
Expand All @@ -43,7 +36,7 @@
@Example(
title = "Clone a private repository from an HTTP server such as a private GitHub repository using a [personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token)",
code = {
"url: https://github.com/anna-geller/kestra-flows",
"url: https://github.com/kestra-io/examples",
"branch: main",
"username: anna-geller",
"password: your_personal_access_token"
Expand All @@ -54,7 +47,7 @@
code = {
"url: git@github.com:kestra-io/kestra.git",
"directory: kestra",
"privateKey: <keyfile>",
"privateKey: <keyfile_content>",
"passphrase: <passphrase>"
}
),
Expand All @@ -71,78 +64,43 @@
" tasks:",
" - id: cloneRepository",
" type: io.kestra.plugin.git.Clone",
" url: https://github.com/anna-geller/kestra-flows",
" url: https://github.com/kestra-io/examples",
" branch: main",

" - id: pythonETL",
" type: io.kestra.plugin.scripts.python.Commands",
" beforeCommands:",
" - pip install requests pandas > /dev/null",
" commands:",
" - ./bin/python flows/etl_script.py",
" - ./bin/python scripts/etl_script.py",
}
)
}
)
public class Clone extends Task implements RunnableTask<Clone.Output> {
@Schema(
title = "The URI to clone from"
)
@PluginProperty(dynamic = true)
@NotNull
private String url;

public class Clone extends AbstractGitTask implements RunnableTask<Clone.Output> {
@Schema(
title = "The optional directory associated with the clone operation.",
description = "If the directory isn't set, the current directory will be used."
)
@PluginProperty(dynamic = true)
private String directory;

@Schema(
title = "The initial Git branch."
)
@PluginProperty(dynamic = true)
private String branch;

@Schema(
title = "Creates a shallow clone with a history truncated to the specified number of commits."
)
@PluginProperty(dynamic = false)
@PluginProperty
@Builder.Default
@Min(1)
private Integer depth = 1;

@Schema(
title = "Whether to clone submodules."
)
@PluginProperty(dynamic = false)
@PluginProperty
private Boolean cloneSubmodules;

@Schema(
title = "The username or organization."
)
@PluginProperty(dynamic = true)
private String username;

@Schema(
title = "The password or personal access token."
)
@PluginProperty(dynamic = true)
private String password;

@Schema(
title = "The private keyfile used to connect."
)
@PluginProperty(dynamic = true)
protected String privateKey;

@Schema(
title = "The passphrase for the `privateKey`."
)
@PluginProperty(dynamic = true)
protected String passphrase;

@Override
public Clone.Output run(RunContext runContext) throws Exception {
Logger logger = runContext.logger();
Expand Down Expand Up @@ -175,35 +133,22 @@ public Clone.Output run(RunContext runContext) throws Exception {
cloneCommand.setCloneSubmodules(this.cloneSubmodules);
}

if (this.username != null && this.password != null) {
cloneCommand.setCredentialsProvider(new UsernamePasswordCredentialsProvider(
runContext.render(this.username),
runContext.render(this.password)
));
}

if (this.privateKey != null) {
Path privateKey = runContext.tempFile(
runContext.render(this.privateKey).getBytes(StandardCharsets.UTF_8),
""
);

Files.setPosixFilePermissions(privateKey, Set.of(PosixFilePermission.OWNER_READ));

cloneCommand.setTransportConfigCallback(new SshTransportConfigCallback(
privateKey.toFile(),
runContext.render(this.passphrase)
));
}
cloneCommand = authentified(cloneCommand, runContext);

logger.info("Start cloning from '{}'", url);

try (Git call = cloneCommand.call()) {
return Output.builder()
.directory(call.getRepository().getDirectory().getAbsolutePath())
.directory(call.getRepository().getDirectory().getParent())
.build();
}
}
}

@Override
@NotNull
public String getUrl() {
return super.getUrl();
}

@Builder
@Getter
Expand Down
33 changes: 31 additions & 2 deletions src/test/java/io/kestra/plugin/git/CloneTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,27 @@

import io.kestra.core.runners.RunContext;
import io.kestra.core.runners.RunContextFactory;
import io.micronaut.context.annotation.Value;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import jakarta.inject.Inject;
import org.apache.commons.io.FileUtils;
import org.junit.jupiter.api.Test;

import java.io.File;
import java.nio.file.Path;
import java.util.Collection;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.*;

@MicronautTest
class CloneTest {
@Inject
private RunContextFactory runContextFactory;

@Value("${kestra.git.pat}")
private String pat;

@Test
void publicRepository() throws Exception {
RunContext runContext = runContextFactory.of();
Expand All @@ -27,6 +33,29 @@ void publicRepository() throws Exception {

Clone.Output runOutput = task.run(runContext);

assertThat(FileUtils.listFiles(Path.of(runOutput.getDirectory()).toFile(), null, true).size(), greaterThan(1));
Collection<File> files = FileUtils.listFiles(Path.of(runOutput.getDirectory()).toFile(), null, true);
assertThat(files, hasItems(
hasProperty("path", endsWith("README.md")),
hasProperty("path", containsString(".git"))
));
}

@Test
void privateRepository() throws Exception {
RunContext runContext = runContextFactory.of();

Clone task = Clone.builder()
.url("https://github.com/kestra-io/unit-tests")
.username(pat)
.password(pat)
.build();

Clone.Output runOutput = task.run(runContext);

Collection<File> files = FileUtils.listFiles(Path.of(runOutput.getDirectory()).toFile(), null, true);
assertThat(files, hasItems(
hasProperty("path", endsWith("README.md")),
hasProperty("path", containsString(".git"))
));
}
}

0 comments on commit f7fa540

Please sign in to comment.