add nextflow d30e48d
This commit is contained in:
73
nextflow/plugins/nf-codecommit/README.md
Normal file
73
nextflow/plugins/nf-codecommit/README.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# AWS CodeCommit plugin for Nextflow
|
||||
|
||||
## Summary
|
||||
|
||||
The AWS CodeCommit plugin provides integration with AWS CodeCommit. It enables Nextflow to pull pipeline scripts directly from CodeCommit repositories.
|
||||
|
||||
## Get Started
|
||||
|
||||
To use this plugin, add it to your `nextflow.config`:
|
||||
|
||||
```groovy
|
||||
plugins {
|
||||
id 'nf-codecommit'
|
||||
}
|
||||
```
|
||||
|
||||
The plugin enables Nextflow to recognize CodeCommit repository URLs and authenticate using your AWS credentials.
|
||||
|
||||
Run a pipeline directly from CodeCommit:
|
||||
|
||||
```bash
|
||||
nextflow run codecommit://my-repo/main.nf
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Running a Pipeline from CodeCommit
|
||||
|
||||
```bash
|
||||
nextflow run codecommit://my-pipeline-repo/main.nf
|
||||
```
|
||||
|
||||
### Specifying a Branch or Tag
|
||||
|
||||
```bash
|
||||
nextflow run codecommit://my-pipeline-repo/main.nf -r develop
|
||||
```
|
||||
|
||||
### Using a Specific AWS Region
|
||||
|
||||
```bash
|
||||
nextflow run codecommit://my-pipeline-repo/main.nf -hub codecommit -hub-opts region=eu-west-1
|
||||
```
|
||||
|
||||
### Configuration with AWS Region
|
||||
|
||||
```groovy
|
||||
plugins {
|
||||
id 'nf-codecommit'
|
||||
}
|
||||
|
||||
aws {
|
||||
region = 'us-east-1'
|
||||
}
|
||||
```
|
||||
|
||||
### Using AWS Profiles
|
||||
|
||||
Set the AWS profile via environment variable:
|
||||
|
||||
```bash
|
||||
export AWS_PROFILE=my-profile
|
||||
nextflow run codecommit://my-repo/main.nf
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [AWS CodeCommit Documentation](https://docs.aws.amazon.com/codecommit/)
|
||||
- [Nextflow Pipeline Sharing](https://nextflow.io/docs/latest/sharing.html)
|
||||
|
||||
## License
|
||||
|
||||
[Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0)
|
||||
1
nextflow/plugins/nf-codecommit/VERSION
Normal file
1
nextflow/plugins/nf-codecommit/VERSION
Normal file
@@ -0,0 +1 @@
|
||||
0.5.1
|
||||
65
nextflow/plugins/nf-codecommit/build.gradle
Normal file
65
nextflow/plugins/nf-codecommit/build.gradle
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2013-2026, Seqera Labs
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
plugins {
|
||||
id 'io.nextflow.nextflow-plugin' version "${nextflowPluginVersion}"
|
||||
id 'java-test-fixtures'
|
||||
}
|
||||
|
||||
nextflowPlugin {
|
||||
nextflowVersion = '25.09.0-edge'
|
||||
|
||||
provider = "${nextflowPluginProvider}"
|
||||
description = 'Provides seamless integration with AWS CodeCommit repositories for workflow source code management and version control'
|
||||
className = 'nextflow.cloud.aws.codecommit.AwsCodeCommitPlugin'
|
||||
useDefaultDependencies = false
|
||||
generateSpec = false
|
||||
extensionPoints = [
|
||||
'nextflow.cloud.aws.codecommit.AwsCodeCommitFactory'
|
||||
]
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs = []
|
||||
main.groovy.srcDirs = ['src/main']
|
||||
main.resources.srcDirs = ['src/resources']
|
||||
test.groovy.srcDirs = ['src/test']
|
||||
test.java.srcDirs = []
|
||||
test.resources.srcDirs = ['src/testResources']
|
||||
}
|
||||
|
||||
configurations {
|
||||
// see https://docs.gradle.org/4.1/userguide/dependency_management.html#sub:exclude_transitive_dependencies
|
||||
runtimeClasspath.exclude group: 'org.slf4j', module: 'slf4j-api'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly project(':nextflow')
|
||||
compileOnly 'org.slf4j:slf4j-api:2.0.17'
|
||||
compileOnly 'org.pf4j:pf4j:3.14.1'
|
||||
|
||||
api ('javax.xml.bind:jaxb-api:2.4.0-b180830.0359')
|
||||
api ('software.amazon.awssdk:codecommit:2.31.64')
|
||||
api ('software.amazon.awssdk:sso:2.31.64')
|
||||
api ('software.amazon.awssdk:ssooidc:2.31.64')
|
||||
|
||||
// address security vulnerabilities
|
||||
runtimeOnly 'io.netty:netty-codec-http:4.1.132.Final'
|
||||
|
||||
testImplementation(testFixtures(project(":nextflow")))
|
||||
testImplementation project(':nextflow')
|
||||
testImplementation "org.apache.groovy:groovy:4.0.31"
|
||||
testImplementation "org.apache.groovy:groovy-nio:4.0.31"
|
||||
}
|
||||
56
nextflow/plugins/nf-codecommit/changelog.txt
Normal file
56
nextflow/plugins/nf-codecommit/changelog.txt
Normal file
@@ -0,0 +1,56 @@
|
||||
nf-codecommit changelog
|
||||
=======================
|
||||
0.5.1 - 26 Mar 2026
|
||||
- Fix netty and jackson vulnerabilities (#6955) [8dafdd95d]
|
||||
|
||||
0.5.0 - 8 Oct 2025
|
||||
- Add listDirectory traversal API to RepositoryProvider abstraction (#6430) [1449fdfec]
|
||||
|
||||
0.3.0 - 15 Aug 2025
|
||||
- Bump groovy 4.0.28 (#6304) [a468f8ef]
|
||||
- Revert "Update nf-codecommit to AWS SDK v2 (#6263)" [5542351c]
|
||||
- Update nf-codecommit to AWS SDK v2 (#6263) [9e9476f2]
|
||||
- Update nf-codecommit to AWS SDK v2 with corrected test (#6293) [1557a91a]
|
||||
|
||||
0.2.3 - 20 Jan 2025
|
||||
- Bump logback 1.5.13 + slf4j 2.0.16 [cc0163ac]
|
||||
- Bump groovy 4.0.24 missing deps [40670f7e]
|
||||
|
||||
0.2.2 - 5 Aug 2024
|
||||
- Bump pf4j to version 3.12.0 [96117b9a]
|
||||
|
||||
0.2.1 - 1 Aug 2024
|
||||
- Bump amazon sdk to version 1.12.766 [5ce42b79]
|
||||
- Bump pf4j to version 3.12.0 [1a8f086a]
|
||||
|
||||
0.2.0 - 5 Feb 2024
|
||||
- Bump Groovy 4 (#4443) [9d32503b]
|
||||
|
||||
0.1.6 - 24 Nov 2023
|
||||
- Fix security vulnerabilities (#4513) [a310c777]
|
||||
- Bump javax.xml.bind:jaxb-api:2.4.0-b180830.0359
|
||||
|
||||
0.1.5-patch2 - 30 Jul 2024
|
||||
- Bump amazon sdk to version 1.12.766 [189f58ed]
|
||||
- Bump pf4j to version 3.12.0 [8dfa4076]
|
||||
|
||||
0.1.5-patch1 - 28 May 2024
|
||||
- Bump dependency with Nextflow 23.10.2
|
||||
|
||||
0.1.5 - 15 May 2023
|
||||
- Update logging libraries [d7eae86e]
|
||||
|
||||
0.1.4 - 15 Apr 2023
|
||||
- Bump aws-java-sdk-s3:1.12.429 [b71ee0a3]
|
||||
|
||||
0.1.3 - 14 Jan 2023
|
||||
- Bump groovy 3.0.14 [6f3ed6e8]
|
||||
|
||||
0.1.2 - 17 Jun 2022
|
||||
- Fix CodeCommit credentials usage
|
||||
|
||||
0.1.1 - 16 Jun 2022
|
||||
- [INVALID]
|
||||
|
||||
0.1.0 - 9 Jun 2022
|
||||
- AWS CodeCommit initial release
|
||||
@@ -0,0 +1,324 @@
|
||||
/*
|
||||
* Copyright 2013-2026, Seqera Labs
|
||||
*
|
||||
* 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 nextflow.cloud.aws.codecommit
|
||||
|
||||
import software.amazon.awssdk.auth.credentials.AwsCredentials
|
||||
|
||||
import javax.crypto.Mac
|
||||
|
||||
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import javax.security.auth.login.CredentialException
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider
|
||||
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider
|
||||
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider
|
||||
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials
|
||||
import software.amazon.awssdk.auth.credentials.AwsSessionCredentials
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.util.logging.Slf4j
|
||||
import nextflow.exception.AbortOperationException
|
||||
import org.eclipse.jgit.errors.UnsupportedCredentialItem
|
||||
import org.eclipse.jgit.transport.CredentialItem
|
||||
import org.eclipse.jgit.transport.CredentialsProvider
|
||||
import org.eclipse.jgit.transport.URIish
|
||||
/**
|
||||
* Provides a jgit {@link CredentialsProvider} implementation that can provide the
|
||||
* appropriate credentials to connect to an AWS CodeCommit repository.
|
||||
*
|
||||
* From the command line, you can configure git to use AWS CodeCommit with a credential
|
||||
* helper. Although jgit does not support credential helper commands, it provides
|
||||
* a CredentialsProvider abstract class we can extend. Connecting to an AWS CodeCommit
|
||||
* (codecommit) repository requires an AWS access key and secret key. These are used to
|
||||
* calculate a signature for the git request. The AWS access key is used as the codecommit
|
||||
* username, and the calculated signature is used as the password. The process for
|
||||
* calculating this signature is documented at
|
||||
* https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html.
|
||||
*
|
||||
* @author Don Laidlaw
|
||||
*
|
||||
* This class is mostly a direct port of the AwsCodeCommitCredentialProvider class provided by the
|
||||
* spring framework in org.springframwork.cloud.config.server.support, with some minor
|
||||
* modifications and simplifications that leverage the Groovy language.
|
||||
*
|
||||
* @author W. Lee Pang <wleepang@gmail.com>
|
||||
*/
|
||||
@Slf4j
|
||||
@CompileStatic
|
||||
final class AwsCodeCommitCredentialProvider extends CredentialsProvider {
|
||||
|
||||
private static final String SHA_256 = "SHA-256"
|
||||
private static final String UTF8 = "UTF8"
|
||||
private static final String HMAC_SHA256 = "HmacSHA256"
|
||||
|
||||
private String username
|
||||
private String password
|
||||
|
||||
/**
|
||||
* Calculate the AWS CodeCommit password for the provided URI and AWS secret key. This
|
||||
* uses the algorithm published by AWS at
|
||||
* https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
|
||||
* @param uri the codecommit repository uri
|
||||
* @param awsSecretKey the aws secret key
|
||||
* @return the password to use in the git request
|
||||
*/
|
||||
protected static String calculateCodeCommitPassword(URIish uri, String awsSecretKey, Date now) {
|
||||
String[] split = uri.getHost().split("\\.")
|
||||
if (split.length < 4) {
|
||||
throw new CredentialException("Cannot detect AWS region from URI $uri")
|
||||
}
|
||||
String region = split[1]
|
||||
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss")
|
||||
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"))
|
||||
|
||||
String dateStamp = dateFormat.format(now)
|
||||
String shortDateStamp = dateStamp.substring(0, 8)
|
||||
|
||||
String codeCommitPassword
|
||||
try {
|
||||
def stringToSign = "AWS4-HMAC-SHA256\n${dateStamp}\n${shortDateStamp}/$region/codecommit/aws4_request\n${bytesToHexString(canonicalRequestDigest(uri))}"
|
||||
def signedRequest = bytesToHexString(
|
||||
sign(awsSecretKey, shortDateStamp, region, stringToSign)
|
||||
)
|
||||
|
||||
codeCommitPassword = "${dateStamp}Z${signedRequest}"
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException("Error calculating AWS CodeCommit password", e)
|
||||
}
|
||||
|
||||
return codeCommitPassword
|
||||
}
|
||||
|
||||
private static byte[] hmacSha256(String data, byte[] key) {
|
||||
String algorithm = HMAC_SHA256
|
||||
Mac mac = Mac.getInstance(algorithm)
|
||||
mac.init(new SecretKeySpec(key, algorithm))
|
||||
return mac.doFinal(data.getBytes(UTF8))
|
||||
}
|
||||
|
||||
private static byte[] sign(String secret, String shortDateStamp, String region, String toSign) {
|
||||
byte[] kSecret = ("AWS4" + secret).getBytes(UTF8)
|
||||
byte[] kDate = hmacSha256(shortDateStamp, kSecret)
|
||||
byte[] kRegion = hmacSha256(region, kDate)
|
||||
byte[] kService = hmacSha256("codecommit", kRegion)
|
||||
byte[] kSigning = hmacSha256("aws4_request", kService)
|
||||
return hmacSha256(toSign, kSigning)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a message digest.
|
||||
* @param uri uri to process
|
||||
* @return a message digest
|
||||
* @throws NoSuchAlgorithmException when the SHA 256 algorithm is not found
|
||||
*/
|
||||
private static byte[] canonicalRequestDigest(URIish uri) {
|
||||
def canonicalRequest = ""
|
||||
|
||||
// this could be done faster with a templated multi-line GString, but is broken out here
|
||||
// to maintain documentation of each part
|
||||
canonicalRequest += "GIT\n" // codecommit uses GIT as the request method
|
||||
canonicalRequest += "${uri.getPath()}\n" // URI request path
|
||||
canonicalRequest += "\n" // Query string, always empty for codecommit
|
||||
|
||||
// Next are canonical headers — codecommit only requires the host header
|
||||
canonicalRequest += "host:${uri.getHost()}\n\n" // canonical headers are alays terminated with \n
|
||||
canonicalRequest += "host\n" // The list of canonical headers, only one for codecommit
|
||||
|
||||
MessageDigest digest = MessageDigest.getInstance(SHA_256);
|
||||
|
||||
return digest.digest(canonicalRequest.getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert bytes to a hex string.
|
||||
* @param bytes the bytes
|
||||
* @return a string of hex characters encoding the bytes.
|
||||
*/
|
||||
private static String bytesToHexString(byte[] bytes) { bytes.encodeHex().toString() }
|
||||
|
||||
/**
|
||||
* This provider can handle uris like
|
||||
* https://git-codecommit.$AWS_REGION.amazonaws.com/v1/repos/$REPO .
|
||||
* @param uri uri to parse
|
||||
* @return {@code true} if the URI can be handled
|
||||
*/
|
||||
static boolean canHandle(String uri) {
|
||||
if ( !uri?.trim() ) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
URL url = new URL(uri)
|
||||
URI u = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(),
|
||||
url.getPort(), url.getPath(), url.getQuery(), url.getRef())
|
||||
if (u.getScheme().equals("https")) {
|
||||
String host = u.getHost()
|
||||
if (host.endsWith(".amazonaws.com")
|
||||
&& host.startsWith("git-codecommit.")) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Throwable t) {
|
||||
// ignore all, we can't handle it
|
||||
log.debug "AWS CodeCommit cannot handle uri: $uri - Reason: ${t.message ?: t}"
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the AWSCredentials. If an AwsCredentialProvider was specified, use that,
|
||||
* otherwise, create a new AwsCredentialsProvider. If the username and password are
|
||||
* provided, use those directly as AwsCredentials. Otherwise, use the
|
||||
* {@link DefaultCredentialsProvider} as is standard with AWS applications.
|
||||
* @return the AWS credentials.
|
||||
*/
|
||||
private AwsCredentials retrieveAwsCredentials() {
|
||||
AwsCredentialsProvider credsProvider
|
||||
if ( username && password ) {
|
||||
log.debug "Creating a static AWS credentials provider"
|
||||
credsProvider = StaticCredentialsProvider.create( AwsBasicCredentials.create(username,password) )
|
||||
}
|
||||
else {
|
||||
log.debug "Creating a default AWS credentials provider chain"
|
||||
credsProvider = DefaultCredentialsProvider.builder().build()
|
||||
}
|
||||
return credsProvider.resolveCredentials()
|
||||
}
|
||||
|
||||
/**
|
||||
* This credentials provider cannot run interactively.
|
||||
* @return false
|
||||
* @see org.eclipse.jgit.transport.CredentialsProvider#isInteractive()
|
||||
*/
|
||||
@Override
|
||||
boolean isInteractive() { false }
|
||||
|
||||
/**
|
||||
* We support username and password credential items only.
|
||||
* @see org.eclipse.jgit.transport.CredentialsProvider#supports(org.eclipse.jgit.transport.CredentialItem[])
|
||||
*/
|
||||
@Override
|
||||
boolean supports(CredentialItem... items) {
|
||||
for ( i in items ) {
|
||||
if (i instanceof CredentialItem.Username) {
|
||||
continue
|
||||
}
|
||||
else if (i instanceof CredentialItem.Password) {
|
||||
continue
|
||||
}
|
||||
else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the username and password to use for the given uri.
|
||||
* @see org.eclipse.jgit.transport.CredentialsProvider#get(org.eclipse.jgit.transport.URIish,
|
||||
* org.eclipse.jgit.transport.CredentialItem[])
|
||||
*/
|
||||
@Override
|
||||
boolean get(URIish uri, CredentialItem... items) {
|
||||
String codeCommitPassword
|
||||
String awsAccessKey
|
||||
String awsSecretKey
|
||||
|
||||
try {
|
||||
AwsCredentials awsCredentials = retrieveAwsCredentials()
|
||||
StringBuilder awsKey = new StringBuilder();
|
||||
awsKey.append(awsCredentials.accessKeyId());
|
||||
awsSecretKey = awsCredentials.secretAccessKey();
|
||||
if (awsCredentials instanceof AwsSessionCredentials) {
|
||||
AwsSessionCredentials sessionCreds = (AwsSessionCredentials) awsCredentials;
|
||||
if ( sessionCreds.sessionToken() ) {
|
||||
awsKey.append('%').append(sessionCreds.sessionToken())
|
||||
}
|
||||
}
|
||||
awsAccessKey = awsKey.toString()
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new AbortOperationException("Unable to retrieve AWS Credentials", e)
|
||||
}
|
||||
|
||||
try {
|
||||
codeCommitPassword = calculateCodeCommitPassword(uri, awsSecretKey, new Date());
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new AbortOperationException("Error calculating AWS CodeCommit password", e)
|
||||
}
|
||||
|
||||
for ( i in items ) {
|
||||
if (i instanceof CredentialItem.Username) {
|
||||
((CredentialItem.Username) i).setValue(awsAccessKey);
|
||||
log.trace("Returning username " + awsAccessKey);
|
||||
continue;
|
||||
}
|
||||
if (i instanceof CredentialItem.Password) {
|
||||
((CredentialItem.Password) i).setValue(codeCommitPassword.toCharArray());
|
||||
log.trace("Returning password " + codeCommitPassword);
|
||||
continue;
|
||||
}
|
||||
if (i instanceof CredentialItem.StringType
|
||||
&& i.getPromptText().equals("Password: ")) {
|
||||
((CredentialItem.StringType) i).setValue(codeCommitPassword);
|
||||
log.trace("Returning password string " + codeCommitPassword);
|
||||
continue;
|
||||
}
|
||||
throw new UnsupportedCredentialItem(uri, i.getClass().getName() + ":" + i.getPromptText());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw out cached data and force retrieval of AWS credentials.
|
||||
* @param uri This parameter is not used in this implementation.
|
||||
*/
|
||||
@Override
|
||||
void reset(URIish uri) {
|
||||
// Should throw out cached info.
|
||||
// Note that even though the credentials (password) we calculate here is
|
||||
// valid for 15 minutes, we do not cache it. Instead, we re-calculate
|
||||
// it each time it is needed. However, the AWSCredentialProvider will cache
|
||||
// its AWSCredentials object.
|
||||
}
|
||||
|
||||
/**
|
||||
* @param awsCredentialProvider the awsCredentialProvider to set
|
||||
*/
|
||||
void setAwsCredentialsProvider(AwsCredentialsProvider awsCredentialsProvider) {
|
||||
this.awsCredentialsProvider = awsCredentialsProvider
|
||||
}
|
||||
|
||||
String getUsername() {
|
||||
return username
|
||||
}
|
||||
|
||||
String getPassword() {
|
||||
return password
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2013-2026, Seqera Labs
|
||||
*
|
||||
* 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 nextflow.cloud.aws.codecommit
|
||||
|
||||
import groovy.util.logging.Slf4j
|
||||
import nextflow.plugin.Priority
|
||||
import nextflow.scm.GitUrl
|
||||
import nextflow.scm.ProviderConfig
|
||||
import nextflow.scm.RepositoryFactory
|
||||
import nextflow.scm.RepositoryProvider
|
||||
/**
|
||||
* Implements a factory to create an instance of {@link AwsCodeCommitRepositoryProvider}
|
||||
*
|
||||
* @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
|
||||
*/
|
||||
@Slf4j
|
||||
@Priority(-10) // <-- lower is higher, this is needed to override default provider behavior
|
||||
class AwsCodeCommitFactory extends RepositoryFactory {
|
||||
|
||||
@Override
|
||||
protected RepositoryProvider createProviderInstance(ProviderConfig config, String project) {
|
||||
return config.platform=='codecommit'
|
||||
? new AwsCodeCommitRepositoryProvider(project,config)
|
||||
: null
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ProviderConfig getConfig(List<ProviderConfig> providers, GitUrl url) {
|
||||
// do not care about non AWS codecommit url
|
||||
if( !url.domain.startsWith('git-codecommit.') || !url.domain.endsWith('.amazonaws.com') )
|
||||
return null
|
||||
|
||||
// CodeCommit hostname vary depending the AWS region
|
||||
// try to find the config for the specified region
|
||||
def config = providers.find( it -> it.domain==url.domain )
|
||||
if( config ) {
|
||||
log.debug "Git url=$url (1) -> config=$config"
|
||||
return config
|
||||
}
|
||||
// fallback on the platform name
|
||||
config = providers.find( it -> it.platform=='codecommit' && !it.server )
|
||||
if( config ) {
|
||||
config.setServer("${url.protocol}://${url.domain}")
|
||||
log.debug "Git url=$url (2) -> config=$config"
|
||||
return config
|
||||
}
|
||||
// still nothing, create a new instance
|
||||
config = new AwsCodeCommitProviderConfig(url.domain)
|
||||
if( url.user ) {
|
||||
log.debug "Git url=$url (3) -> config=$config"
|
||||
config.setUser(url.user)
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ProviderConfig createConfigInstance(String name, Map attrs) {
|
||||
final copy = new HashMap(attrs)
|
||||
if( name == 'codecommit' ) {
|
||||
copy.platform = 'codecommit'
|
||||
}
|
||||
|
||||
return copy.platform == 'codecommit'
|
||||
? new AwsCodeCommitProviderConfig(name, copy)
|
||||
: null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2013-2026, Seqera Labs
|
||||
*
|
||||
* 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 nextflow.cloud.aws.codecommit
|
||||
|
||||
import nextflow.plugin.BasePlugin
|
||||
import org.pf4j.PluginWrapper
|
||||
|
||||
/**
|
||||
* AWS CodeCommit plugin entry point
|
||||
*
|
||||
* @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
|
||||
*/
|
||||
class AwsCodeCommitPlugin extends BasePlugin {
|
||||
|
||||
AwsCodeCommitPlugin(PluginWrapper wrapper) {
|
||||
super(wrapper)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2013-2026, Seqera Labs
|
||||
*
|
||||
* 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 nextflow.cloud.aws.codecommit
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import nextflow.scm.ProviderConfig
|
||||
/**
|
||||
* A {@link ProviderConfig} specialised for AWS CodeCommit
|
||||
*
|
||||
* @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
|
||||
*/
|
||||
@CompileStatic
|
||||
class AwsCodeCommitProviderConfig extends ProviderConfig {
|
||||
|
||||
AwsCodeCommitProviderConfig(String host) {
|
||||
super('codecommit', [platform:'codecommit', server: "https://$host"])
|
||||
assert host =~ /git-codecommit\.[a-z0-9-]+\.amazonaws\.com/, "Invalid AWS CodeCommit hostname: '$host'"
|
||||
}
|
||||
|
||||
AwsCodeCommitProviderConfig(String name, Map attributes) {
|
||||
super(name, attributes)
|
||||
assert attributes.platform=='codecommit', "Invalid AWS CodeCommit platform value: '$attributes.platform'"
|
||||
}
|
||||
|
||||
String getRegion() {
|
||||
final host = getDomain()
|
||||
if( !host )
|
||||
throw new IllegalStateException("Missing AWS CodeCommit repository name")
|
||||
final result = host.tokenize('.')[1]
|
||||
if( !result )
|
||||
throw new IllegalStateException("Invalid AWS CodeCommit hostname: '${host}'")
|
||||
return result
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String resolveProjectName(String path) {
|
||||
assert path
|
||||
assert !path.startsWith('/')
|
||||
final repoName = path.tokenize('/')[-1]
|
||||
if( !repoName )
|
||||
throw new IllegalArgumentException("Invalid AWS CodeCommit repository path: $path")
|
||||
return "codecommit-$region/$repoName"
|
||||
}
|
||||
|
||||
String toString() {
|
||||
return "AwsCodeCommitProviderConfig[name=$name; platform=$platform; server=$server]"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
/*
|
||||
* Copyright 2013-2026, Seqera Labs
|
||||
*
|
||||
* 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 nextflow.cloud.aws.codecommit
|
||||
|
||||
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials
|
||||
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider
|
||||
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider
|
||||
import software.amazon.awssdk.core.exception.SdkException
|
||||
import software.amazon.awssdk.regions.Region
|
||||
import software.amazon.awssdk.services.codecommit.CodeCommitClient
|
||||
import software.amazon.awssdk.services.codecommit.model.CodeCommitException
|
||||
import software.amazon.awssdk.services.codecommit.model.GetFileRequest
|
||||
import software.amazon.awssdk.services.codecommit.model.GetFolderRequest
|
||||
import software.amazon.awssdk.services.codecommit.model.GetRepositoryRequest
|
||||
import software.amazon.awssdk.services.codecommit.model.RepositoryMetadata
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.Memoized
|
||||
import groovy.util.logging.Slf4j
|
||||
import nextflow.exception.AbortOperationException
|
||||
import nextflow.exception.MissingCredentialsException
|
||||
import nextflow.scm.ProviderConfig
|
||||
import nextflow.scm.RepositoryProvider
|
||||
import nextflow.scm.RepositoryProvider.RepositoryEntry
|
||||
import nextflow.util.StringUtils
|
||||
import org.eclipse.jgit.api.errors.TransportException
|
||||
import org.eclipse.jgit.transport.CredentialsProvider
|
||||
/**
|
||||
* Implements a repository provider for AWS CodeCommit
|
||||
*
|
||||
* @author W. Lee Pang <wleepang@gmail.com>
|
||||
* @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
|
||||
*/
|
||||
@Slf4j
|
||||
@CompileStatic
|
||||
class AwsCodeCommitRepositoryProvider extends RepositoryProvider {
|
||||
|
||||
AwsCodeCommitRepositoryProvider(String project, ProviderConfig config) {
|
||||
assert config instanceof AwsCodeCommitProviderConfig
|
||||
this.project = project // expect: "codecommit-<region>/<repository>"
|
||||
this.config = config
|
||||
this.region = config.region
|
||||
this.repositoryName = project.tokenize('/')[-1]
|
||||
this.client = createClient(config)
|
||||
}
|
||||
|
||||
private String region
|
||||
private CodeCommitClient client
|
||||
private String repositoryName
|
||||
|
||||
|
||||
protected CodeCommitClient createClient(AwsCodeCommitProviderConfig config) {
|
||||
final builder = CodeCommitClient.builder()
|
||||
.region(Region.of(region))
|
||||
if( config.user && config.password ) {
|
||||
final creds = AwsBasicCredentials.create(config.user, config.password)
|
||||
log.debug "AWS CodeCommit using username=$config.user; password=${StringUtils.redact(config.password)}"
|
||||
builder.credentialsProvider( StaticCredentialsProvider.create(creds) )
|
||||
}
|
||||
else {
|
||||
log.debug "AWS CodeCommit using default credentials chain"
|
||||
builder.credentialsProvider( DefaultCredentialsProvider.builder().build() )
|
||||
}
|
||||
builder.build()
|
||||
}
|
||||
|
||||
/** {@inheritDoc} **/
|
||||
@Memoized
|
||||
@Override
|
||||
CredentialsProvider getGitCredentials() {
|
||||
return new AwsCodeCommitCredentialProvider(username: user, password: password)
|
||||
}
|
||||
|
||||
private RepositoryMetadata getRepositoryMetadata() {
|
||||
final request = GetRepositoryRequest.builder()
|
||||
.repositoryName(repositoryName)
|
||||
.build()
|
||||
|
||||
return client
|
||||
.getRepository(request)
|
||||
.repositoryMetadata()
|
||||
}
|
||||
|
||||
/** {@inheritDoc} **/
|
||||
// called by AssetManager
|
||||
// used to set credentials for a clone, pull, fetch, operation
|
||||
@Override
|
||||
boolean hasCredentials() {
|
||||
// set to true
|
||||
// uses AWS Credentials instead of username : password
|
||||
// see getGitCredentials()
|
||||
return true
|
||||
}
|
||||
|
||||
/** {@inheritDoc} **/
|
||||
@Override
|
||||
String getName() { "CodeCommit" }
|
||||
|
||||
/** {@inheritDoc} **/
|
||||
@Override
|
||||
String getEndpointUrl() {
|
||||
"https://git-codecommit.${region}.amazonaws.com/v1/repos/${repositoryName}"
|
||||
}
|
||||
|
||||
/** {@inheritDoc} **/
|
||||
// not used, but the abstract method needs to be overridden
|
||||
@Override
|
||||
String getContentUrl( String path ) {
|
||||
throw new UnsupportedOperationException()
|
||||
}
|
||||
|
||||
/** {@inheritDoc} **/
|
||||
// called by AssetManager
|
||||
@Override
|
||||
String getCloneUrl() { getEndpointUrl() }
|
||||
|
||||
/** {@inheritDoc} **/
|
||||
// called by AssetManager
|
||||
@Override
|
||||
String getRepositoryUrl() { getEndpointUrl() }
|
||||
|
||||
/** {@inheritDoc} **/
|
||||
// called by AssetManager
|
||||
// called by RepositoryProvider.readText()
|
||||
@Override
|
||||
byte[] readBytes( String path ) {
|
||||
|
||||
final builder = GetFileRequest.builder()
|
||||
.repositoryName(repositoryName)
|
||||
.filePath(path)
|
||||
if( revision )
|
||||
builder.commitSpecifier(revision)
|
||||
|
||||
try {
|
||||
return client
|
||||
.getFile( builder.build() )
|
||||
.fileContent()?.asByteArray()
|
||||
}
|
||||
catch (Exception e) {
|
||||
checkMissingCredsException(e)
|
||||
log.debug "AWS CodeCommit unable to retrieve file: $path from repo: $repositoryName"
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/** {@inheritDoc} **/
|
||||
@Override
|
||||
List<RepositoryEntry> listDirectory(String path, int depth) {
|
||||
try {
|
||||
// AWS CodeCommit doesn't have a dedicated directory listing API like GitHub
|
||||
// We would need to use GetFolder API, but it has limitations
|
||||
def request = GetFolderRequest.builder()
|
||||
.repositoryName(repositoryName)
|
||||
.folderPath(path ?: "/")
|
||||
.commitSpecifier(revision ?: "HEAD")
|
||||
.build()
|
||||
|
||||
def response = client.getFolder(request)
|
||||
|
||||
List<RepositoryEntry> entries = []
|
||||
|
||||
// Add files
|
||||
response.files()?.each { file ->
|
||||
entries.add(new RepositoryEntry(
|
||||
name: file.relativePath().split('/').last(),
|
||||
path: ensureAbsolutePath(file.relativePath()),
|
||||
type: RepositoryProvider.EntryType.FILE,
|
||||
sha: file.blobId(),
|
||||
size: null // AWS CodeCommit API doesn't provide file size in folder response
|
||||
))
|
||||
}
|
||||
|
||||
// Add subdirectories - but CodeCommit API has limited support for deep traversal
|
||||
response.subFolders()?.each { folder ->
|
||||
entries.add(new RepositoryEntry(
|
||||
name: folder.relativePath().split('/').last(),
|
||||
path: ensureAbsolutePath(folder.relativePath()),
|
||||
type: RepositoryProvider.EntryType.DIRECTORY,
|
||||
sha: null, // CodeCommit doesn't provide SHA for directories
|
||||
size: null
|
||||
))
|
||||
|
||||
// For recursive listing, we would need additional API calls
|
||||
// However, this can be expensive and slow for large repositories
|
||||
if (depth != 0 && depth != 1) {
|
||||
try {
|
||||
def subEntries = listDirectory(folder.relativePath(), depth == -1 ? -1 : depth - 1)
|
||||
entries.addAll(subEntries)
|
||||
} catch (Exception e) {
|
||||
// Continue with other directories if one fails
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entries.sort { it.name }
|
||||
|
||||
} catch (Exception e) {
|
||||
checkMissingCredsException(e)
|
||||
throw new UnsupportedOperationException("Directory listing failed for AWS CodeCommit path: $path - ${e.message}", e)
|
||||
}
|
||||
}
|
||||
|
||||
protected void checkMissingCredsException(Exception e) {
|
||||
final errs = [
|
||||
"Failed to connect to service endpoint",
|
||||
"Unable to load AWS credentials",
|
||||
"The security token included in the request is invalid",
|
||||
"The request signature we calculated does not match the signature you provided"]
|
||||
if( e !instanceof SdkException )
|
||||
return
|
||||
if( e instanceof CodeCommitException && e.message?.startsWith("Could not find path") ) {
|
||||
// it cannot find the request file
|
||||
return
|
||||
}
|
||||
if( errs.find(it-> e.message?.startsWith(it))) {
|
||||
final msg = e.message?.split(/\.|\(|:/)[0].trim()
|
||||
throw new MissingCredentialsException("Missing or invalid AWS CodeCommit credentials - $msg", e)
|
||||
}
|
||||
else {
|
||||
throw new AbortOperationException("Unexpected error while connecting repository - $e.message", e)
|
||||
}
|
||||
}
|
||||
|
||||
/** {@inheritDoc} **/
|
||||
// called by AssetManager
|
||||
@Override
|
||||
void validateRepo() {
|
||||
try {
|
||||
getRepositoryMetadata()
|
||||
}
|
||||
catch( IOException e ) {
|
||||
throw new AbortOperationException("Cannot access ${getEndpointUrl()} - Make sure a repository exists for it in AWS CodeCommit")
|
||||
}
|
||||
}
|
||||
|
||||
private String errMsg(Exception e) {
|
||||
def msg = "Unable to access Git repository"
|
||||
if( e.message )
|
||||
msg + " - ${e.message}"
|
||||
else
|
||||
msg += ": " + getCloneUrl()
|
||||
return msg
|
||||
}
|
||||
@Override
|
||||
List<BranchInfo> getBranches() {
|
||||
try {
|
||||
return super.getBranches()
|
||||
}
|
||||
catch (TransportException e) {
|
||||
throw new AbortOperationException(errMsg(e), e)
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
List<TagInfo> getTags() {
|
||||
try {
|
||||
return super.getTags()
|
||||
}
|
||||
catch (TransportException e) {
|
||||
throw new AbortOperationException(errMsg(e), e)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright 2013-2026, Seqera Labs
|
||||
*
|
||||
* 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 nextflow.cloud.aws.codecommit
|
||||
|
||||
import nextflow.scm.GitUrl
|
||||
import nextflow.scm.ProviderConfig
|
||||
import spock.lang.Specification
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
|
||||
*/
|
||||
class AwsCodeCommitFactoryTest extends Specification {
|
||||
|
||||
def 'should create a new provider instance' () {
|
||||
given:
|
||||
def factory = new AwsCodeCommitFactory()
|
||||
and:
|
||||
def config = Mock(ProviderConfig)
|
||||
|
||||
when:
|
||||
def result = factory.createProviderInstance(config, 'some name')
|
||||
then:
|
||||
result == null
|
||||
and:
|
||||
config.getPlatform() >> 'any'
|
||||
|
||||
}
|
||||
|
||||
def 'should create config instance' () {
|
||||
given:
|
||||
def factory = new AwsCodeCommitFactory()
|
||||
|
||||
when:
|
||||
def result = factory.createConfigInstance('foo', [:])
|
||||
then:
|
||||
result == null
|
||||
|
||||
when:
|
||||
result = factory.createConfigInstance('codecommit', [:])
|
||||
then:
|
||||
result instanceof AwsCodeCommitProviderConfig
|
||||
result.platform == 'codecommit'
|
||||
result.name == 'codecommit'
|
||||
|
||||
when:
|
||||
result = factory.createConfigInstance('my-aws-repo', [platform: 'codecommit', user:'foo', password:'xyz'])
|
||||
then:
|
||||
result instanceof AwsCodeCommitProviderConfig
|
||||
result.platform == 'codecommit'
|
||||
result.name == 'my-aws-repo'
|
||||
result.user == 'foo'
|
||||
result.password == 'xyz'
|
||||
|
||||
}
|
||||
|
||||
def 'should get a config' () {
|
||||
given:
|
||||
def factory = new AwsCodeCommitFactory()
|
||||
|
||||
when:
|
||||
def configs = [
|
||||
new ProviderConfig('github', [:]),
|
||||
new AwsCodeCommitProviderConfig('codecommit', [platform:'codecommit'])
|
||||
]
|
||||
and:
|
||||
def result = factory.getConfig(configs, new GitUrl('https://github.com/this/that'))
|
||||
then:
|
||||
result == null
|
||||
|
||||
/*
|
||||
* A CodeCommit config is given without any server specification
|
||||
* => attributes should be taken and server path updated
|
||||
*/
|
||||
when:
|
||||
configs = [
|
||||
new ProviderConfig('github', [:]),
|
||||
new AwsCodeCommitProviderConfig('codecommit', [platform:'codecommit', 'user':'foo', password: 'xxx'])
|
||||
]
|
||||
and:
|
||||
result = factory.getConfig(configs, new GitUrl('https://git-codecommit.eu-west-1.amazonaws.com/v1/repos/my-repo'))
|
||||
then:
|
||||
result instanceof AwsCodeCommitProviderConfig
|
||||
and:
|
||||
result.name == 'codecommit'
|
||||
result.platform == 'codecommit'
|
||||
result.server == 'https://git-codecommit.eu-west-1.amazonaws.com'
|
||||
result.region == 'eu-west-1'
|
||||
result.user == 'foo'
|
||||
result.password == 'xxx'
|
||||
and:
|
||||
// just modifies the instance in the provided list
|
||||
result in configs
|
||||
|
||||
|
||||
/*
|
||||
* No config is given => a new one should be created
|
||||
*/
|
||||
when:
|
||||
configs = []
|
||||
and:
|
||||
result = factory.getConfig(configs, new GitUrl('https://git-codecommit.eu-west-1.amazonaws.com/v1/repos/my-repo'))
|
||||
then:
|
||||
result instanceof AwsCodeCommitProviderConfig
|
||||
and:
|
||||
result.name == 'codecommit'
|
||||
result.platform == 'codecommit'
|
||||
result.server == 'https://git-codecommit.eu-west-1.amazonaws.com'
|
||||
result.region == 'eu-west-1'
|
||||
and:
|
||||
// creates a new instance
|
||||
result !in configs
|
||||
|
||||
/*
|
||||
* A CodeCommit config with a matching server is given => it should be used
|
||||
*/
|
||||
when:
|
||||
configs = [
|
||||
new ProviderConfig('github', [:]),
|
||||
new AwsCodeCommitProviderConfig('my-repo', [platform:'codecommit', server: 'https://git-codecommit.eu-west-1.amazonaws.com'])
|
||||
]
|
||||
and:
|
||||
result = factory.getConfig(configs, new GitUrl('https://git-codecommit.eu-west-1.amazonaws.com/v1/repos/my-repo'))
|
||||
then:
|
||||
result instanceof AwsCodeCommitProviderConfig
|
||||
and:
|
||||
result.name == 'my-repo'
|
||||
result.platform == 'codecommit'
|
||||
result.server == 'https://git-codecommit.eu-west-1.amazonaws.com'
|
||||
result.region == 'eu-west-1'
|
||||
and:
|
||||
// just modifies the instance in the provided list
|
||||
result in configs
|
||||
|
||||
/*
|
||||
* A CodeCommit config is given for a different region/server
|
||||
* => a new config should be created
|
||||
*/
|
||||
when:
|
||||
configs = [
|
||||
new ProviderConfig('github', [:]),
|
||||
new AwsCodeCommitProviderConfig('my-repo', [platform:'codecommit', server: 'https://git-codecommit.us-east-1.amazonaws.com', user: 'foo'])
|
||||
]
|
||||
and:
|
||||
result = factory.getConfig(configs, new GitUrl('https://myself@git-codecommit.eu-west-1.amazonaws.com/v1/repos/my-repo'))
|
||||
then:
|
||||
result instanceof AwsCodeCommitProviderConfig
|
||||
and:
|
||||
result.name == 'codecommit'
|
||||
result.platform == 'codecommit'
|
||||
result.server == 'https://git-codecommit.eu-west-1.amazonaws.com'
|
||||
result.region == 'eu-west-1'
|
||||
result.user == 'myself'
|
||||
and:
|
||||
result !in configs
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2013-2026, Seqera Labs
|
||||
*
|
||||
* 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 nextflow.cloud.aws.codecommit
|
||||
|
||||
import spock.lang.Specification
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
|
||||
*/
|
||||
class AwsCodeCommitProviderConfigTest extends Specification {
|
||||
|
||||
def 'should create config' () {
|
||||
given:
|
||||
def HOST = 'git-codecommit.eu-west-1.amazonaws.com'
|
||||
when:
|
||||
def config = new AwsCodeCommitProviderConfig(HOST)
|
||||
then:
|
||||
config.name == 'codecommit'
|
||||
config.platform == 'codecommit'
|
||||
config.region == 'eu-west-1'
|
||||
config.domain == 'git-codecommit.eu-west-1.amazonaws.com'
|
||||
config.server == 'https://git-codecommit.eu-west-1.amazonaws.com'
|
||||
config.endpoint == 'https://git-codecommit.eu-west-1.amazonaws.com'
|
||||
|
||||
expect:
|
||||
config.resolveProjectName('https://git-codecommit.eu-west-1.amazonaws.com/v1/repos/my-repo') == 'codecommit-eu-west-1/my-repo'
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright 2013-2026, Seqera Labs
|
||||
*
|
||||
* 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 nextflow.cloud.aws.codecommit
|
||||
|
||||
import nextflow.scm.RepositoryProvider
|
||||
import spock.lang.IgnoreIf
|
||||
import spock.lang.Requires
|
||||
import spock.lang.Specification
|
||||
/**
|
||||
*
|
||||
* @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
|
||||
*/
|
||||
@IgnoreIf({System.getenv('NXF_SMOKE')})
|
||||
@Requires({System.getenv('AWS_ACCESS_KEY_ID') && System.getenv('AWS_SECRET_ACCESS_KEY')})
|
||||
class AwsCodeCommitRepositoryProviderTest extends Specification {
|
||||
|
||||
def 'should get repo url' () {
|
||||
given:
|
||||
def config = new AwsCodeCommitProviderConfig('git-codecommit.eu-west-1.amazonaws.com')
|
||||
and:
|
||||
def provider = new AwsCodeCommitRepositoryProvider('codecommit-eu-west-1/my-repo', config)
|
||||
|
||||
expect:
|
||||
provider.getCloneUrl() == 'https://git-codecommit.eu-west-1.amazonaws.com/v1/repos/my-repo'
|
||||
and:
|
||||
provider.getRepositoryUrl() == "https://git-codecommit.eu-west-1.amazonaws.com/v1/repos/my-repo"
|
||||
}
|
||||
|
||||
def 'should read content' () {
|
||||
given:
|
||||
def config = new AwsCodeCommitProviderConfig('git-codecommit.eu-west-1.amazonaws.com')
|
||||
and:
|
||||
def provider = new AwsCodeCommitRepositoryProvider('codecommit-eu-west-1/my-repo', config)
|
||||
|
||||
expect:
|
||||
provider.readText('main.nf') == '''\
|
||||
nextflow.enable.dsl=2
|
||||
|
||||
workflow {
|
||||
sayHello()
|
||||
}
|
||||
|
||||
process sayHello {
|
||||
/echo Hello world/
|
||||
}
|
||||
'''.stripIndent().rightTrim()
|
||||
}
|
||||
|
||||
def 'should read content with revision' () {
|
||||
given:
|
||||
def config = new AwsCodeCommitProviderConfig('git-codecommit.eu-west-1.amazonaws.com')
|
||||
and:
|
||||
def provider = new AwsCodeCommitRepositoryProvider('codecommit-eu-west-1/my-repo', config)
|
||||
and:
|
||||
provider.revision = 'dev1'
|
||||
expect:
|
||||
provider.readText('main.nf') == 'println "Hello world in dev branch!"\n'
|
||||
}
|
||||
|
||||
def 'should fetch repo tags'() {
|
||||
given:
|
||||
def config = new AwsCodeCommitProviderConfig('git-codecommit.eu-west-1.amazonaws.com')
|
||||
and:
|
||||
def provider = new AwsCodeCommitRepositoryProvider('codecommit-eu-west-1/my-repo', config)
|
||||
|
||||
when:
|
||||
// uses repo at
|
||||
// https://git-codecommit.eu-west-1.amazonaws.com/v1/repos/my-repo
|
||||
def result = provider.getTags() as Set
|
||||
then:
|
||||
result == [
|
||||
new RepositoryProvider.TagInfo('v0.1', '1a88516b5e382d0d68bfa01c18eab6c2067c0595'),
|
||||
new RepositoryProvider.TagInfo('v0.2', 'c673d3d55be190c54db2056690b71e285fe5b3d8')] as Set
|
||||
|
||||
}
|
||||
|
||||
def 'should fetch repo branches'() {
|
||||
given:
|
||||
def config = new AwsCodeCommitProviderConfig('git-codecommit.eu-west-1.amazonaws.com')
|
||||
and:
|
||||
def provider = new AwsCodeCommitRepositoryProvider('codecommit-eu-west-1/my-repo', config)
|
||||
|
||||
when:
|
||||
// uses repo at
|
||||
// https://git-codecommit.eu-west-1.amazonaws.com/v1/repos/my-repo
|
||||
def result = provider.getBranches() as Set
|
||||
then:
|
||||
result == [
|
||||
new RepositoryProvider.BranchInfo('master', 'c820e0904d9ce4404e005e3cc910502300b36ba3'),
|
||||
new RepositoryProvider.BranchInfo('dev1', 'c90422a1b4823f1c0980bbf8cab261e45a351622')] as Set
|
||||
|
||||
}
|
||||
|
||||
def 'should list root directory contents'() {
|
||||
given:
|
||||
def config = new AwsCodeCommitProviderConfig('git-codecommit.eu-west-1.amazonaws.com')
|
||||
def provider = new AwsCodeCommitRepositoryProvider('codecommit-eu-west-1/my-repo', config)
|
||||
|
||||
when:
|
||||
def entries = provider.listDirectory("/", 1)
|
||||
|
||||
then:
|
||||
entries.size() > 0
|
||||
and:
|
||||
entries.any { it.name == 'main.nf' && it.type == RepositoryProvider.EntryType.FILE }
|
||||
and:
|
||||
entries.every { it.path && it.name && it.sha }
|
||||
// Should only include immediate children for depth=1
|
||||
entries.every { it.path.split('/').length <= 2 }
|
||||
}
|
||||
|
||||
def 'should list directory contents recursively'() {
|
||||
given:
|
||||
def config = new AwsCodeCommitProviderConfig('git-codecommit.eu-west-1.amazonaws.com')
|
||||
def provider = new AwsCodeCommitRepositoryProvider('codecommit-eu-west-1/my-repo', config)
|
||||
|
||||
when:
|
||||
def entries = provider.listDirectory("/", 10)
|
||||
|
||||
then:
|
||||
entries.size() > 0
|
||||
and:
|
||||
// Should include files from root and potentially subdirectories
|
||||
entries.any { it.name == 'main.nf' && it.type == RepositoryProvider.EntryType.FILE }
|
||||
and:
|
||||
entries.every { it.path && it.name && it.sha }
|
||||
}
|
||||
|
||||
def 'should list directory contents with depth 2'() {
|
||||
given:
|
||||
def config = new AwsCodeCommitProviderConfig('git-codecommit.eu-west-1.amazonaws.com')
|
||||
def provider = new AwsCodeCommitRepositoryProvider('codecommit-eu-west-1/my-repo', config)
|
||||
|
||||
when:
|
||||
def depthOne = provider.listDirectory("/", 1)
|
||||
def depthTwo = provider.listDirectory("/", 2)
|
||||
|
||||
then:
|
||||
depthOne.size() > 0
|
||||
depthTwo.size() >= depthOne.size()
|
||||
and:
|
||||
depthOne.every { it.path && it.name && it.sha }
|
||||
depthTwo.every { it.path && it.name && it.sha }
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user