Skip to content

Fix readBytes for non-GitHub providers #6243

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 5, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,7 @@ final class BitbucketRepositoryProvider extends RepositoryProvider {

@Override
byte[] readBytes(String path) {

def url = getContentUrl(path)
invoke(url)?.getBytes()
final url = getContentUrl(path)
return invokeBytes(url)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ final class BitbucketServerRepositoryProvider extends RepositoryProvider {

@Override
byte[] readBytes(String path) {
def url = getContentUrl(path)
invoke(url)?.getBytes()
final url = getContentUrl(path)
return invokeBytes(url)
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,8 @@ final class GiteaRepositoryProvider extends RepositoryProvider {
/** {@inheritDoc} */
@Override
byte[] readBytes(String path) {

def url = getContentUrl(path)
def contents = invoke(url)
return contents?.getBytes()
return invokeBytes(url)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,9 @@ class GithubRepositoryProvider extends RepositoryProvider {
/** {@inheritDoc} */
@Override
byte[] readBytes(String path) {

def url = getContentUrl(path)
Map response = invokeAndParseResponse(url)
response.get('content')?.toString()?.decodeBase64()

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,8 @@ class GitlabRepositoryProvider extends RepositoryProvider {
/** {@inheritDoc} */
@Override
byte[] readBytes(String path) {

def url = getContentUrl(path)
Map response = invokeAndParseResponse(url)
response.get('content')?.toString()?.decodeBase64()

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -224,14 +224,25 @@ abstract class RepositoryProvider {
* @return The remote service response as a text
*/
protected String invoke( String api ) {
final result = invokeBytes(api)
return result!=null ? new String(result) : null
}

/**
* Invoke the API request specified and return binary content
*
* @param api A API request url e.g. https://api.github.com/repos/nextflow-io/hello/raw/image.png
* @return The remote service response as byte array
*/
protected byte[] invokeBytes( String api ) {
assert api
log.debug "Request [credentials ${getAuthObfuscated() ?: '-'}] -> $api"
final request = newRequest(api)
// submit the request
final HttpResponse<String> resp = httpSend(request)
final HttpResponse<byte[]> resp = httpSend0(request)
// check the response code
checkResponse(resp)
// return the body as string
// return the body as byte array
return resp.body()
}

Expand Down Expand Up @@ -263,7 +274,7 @@ abstract class RepositoryProvider {
*
* @param response A {@link HttpURLConnection} response instance
*/
protected checkResponse( HttpResponse<String> response ) {
protected checkResponse( HttpResponse<?> response ) {
final code = response.statusCode()
if( code==401 ) {
log.debug "Response status: $code -- ${response.body()}"
Expand Down Expand Up @@ -447,6 +458,7 @@ abstract class RepositoryProvider {
return isCausedByUnresolvedAddressException(t.cause)
}

@Deprecated
protected HttpResponse<String> httpSend(HttpRequest request) {
if( httpClient==null )
httpClient = newHttpClient()
Expand All @@ -460,10 +472,24 @@ abstract class RepositoryProvider {
}
}

private HttpResponse<byte[]> httpSend0(HttpRequest request) {
if( httpClient==null )
httpClient = newHttpClient()
if( retryConfig==null )
retryConfig = new RetryConfig()
try {
safeApply(()-> httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray()))
}
catch (FailsafeException e) {
throw e.cause
}
}

private HttpClient newHttpClient() {
final builder = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_1_1)
.connectTimeout(Duration.ofSeconds(60))
.followRedirects(HttpClient.Redirect.NORMAL)
// use virtual threads executor if enabled
if( Threads.useVirtual() )
builder.executor(Executors.newVirtualThreadPerTaskExecutor())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ class AzureRepositoryProviderTest extends Specification {
't-neumann/hello' | ['t-neumann', 'hello', 'hello']
'ORGANIZATION/PROJECT/hello' | ['ORGANIZATION','PROJECT','hello']
'ORGANIZATION/PROJECT/_git/hello' | ['ORGANIZATION','PROJECT','hello']

}

def 'should throw exception if wrong path' () {
Expand All @@ -69,7 +68,6 @@ class AzureRepositoryProviderTest extends Specification {
}

def 'should return repo url' () {

given:
def config = new ConfigSlurper().parse(CONFIG)
def obj = new ProviderConfig('azurerepos', config.providers.azurerepos as ConfigObject)
Expand All @@ -85,7 +83,6 @@ class AzureRepositoryProviderTest extends Specification {
}

def 'should return project URL' () {

given:
def config = new ConfigSlurper().parse(CONFIG)
def obj = new ProviderConfig('azurerepos', config.providers.azurerepos as ConfigObject)
Expand All @@ -97,29 +94,24 @@ class AzureRepositoryProviderTest extends Specification {
't-neumann/hello' | 'https://dev.azure.com/t-neumann/hello'
'ORGANIZATION/PROJECT/hello' | 'https://dev.azure.com/ORGANIZATION/PROJECT/hello'
'ORGANIZATION/PROJECT/_git/hello' | 'https://dev.azure.com/ORGANIZATION/PROJECT/_git/hello'

}

def 'should return content URL' () {

given:
def config = new ConfigSlurper().parse(CONFIG)
def obj = new ProviderConfig('azurerepos', config.providers.azurerepos as ConfigObject)

expect:
new AzureRepositoryProvider('t-neumann/hello', obj).getContentUrl('main.nf') == 'https://dev.azure.com/t-neumann/hello/_apis/git/repositories/hello/items?download=false&includeContent=true&includeContentMetadata=false&api-version=6.0&\$format=json&path=main.nf'

}

def 'should return content URL for revision' () {

given:
def config = new ConfigSlurper().parse(CONFIG)
def obj = new ProviderConfig('azurerepos', config.providers.azurerepos as ConfigObject)

expect:
new AzureRepositoryProvider('t-neumann/hello', obj).setRevision("a-branch").getContentUrl('main.nf') == 'https://dev.azure.com/t-neumann/hello/_apis/git/repositories/hello/items?download=false&includeContent=true&includeContentMetadata=false&api-version=6.0&\$format=json&path=main.nf&versionDescriptor.version=a-branch'

}

/*
Expand All @@ -141,7 +133,6 @@ class AzureRepositoryProviderTest extends Specification {
def result = repo.readText('main.nf')
then:
result == 'println "Hello from Azure repos!"'

}

@IgnoreIf({System.getenv('NXF_SMOKE')})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ class BitbucketRepositoryProviderTest extends Specification {

@Requires( { System.getenv('NXF_BITBUCKET_ACCESS_TOKEN') } )
def testBitbucketCloneURL() {

given:
def token = System.getenv('NXF_BITBUCKET_ACCESS_TOKEN')
def config = new ProviderConfig('bitbucket').setAuth(token)
Expand All @@ -39,16 +38,13 @@ class BitbucketRepositoryProviderTest extends Specification {
url == "https://${config.user}@bitbucket.org/pditommaso/tutorial.git".toString()
}


def testGetHomePage() {
expect:
new BitbucketRepositoryProvider('pditommaso/tutorial').getRepositoryUrl() == "https://bitbucket.org/pditommaso/tutorial"
}


@Requires( { System.getenv('NXF_BITBUCKET_ACCESS_TOKEN') } )
def testReadContent() {

given:
def token = System.getenv('NXF_BITBUCKET_ACCESS_TOKEN')
def config = new ProviderConfig('bitbucket').setAuth(token)
Expand All @@ -59,7 +55,21 @@ class BitbucketRepositoryProviderTest extends Specification {

then:
result.trim().startsWith('#!/usr/bin/env nextflow')
}

@Requires( { System.getenv('NXF_BITBUCKET_ACCESS_TOKEN') } )
def 'should read binary data'() {
given:
def token = System.getenv('NXF_BITBUCKET_ACCESS_TOKEN')
def config = new ProviderConfig('bitbucket').setAuth(token)
def DATA = this.class.getResourceAsStream('/test-sandbucket.jpg').bytes

when:
def repo = new BitbucketRepositoryProvider('pditommaso/tutorial', config)
def result = repo.readBytes('sandbucket.jpg')

then:
result == DATA
}

@Requires( { System.getenv('NXF_BITBUCKET_ACCESS_TOKEN') } )
Expand Down Expand Up @@ -160,6 +170,5 @@ class BitbucketRepositoryProviderTest extends Specification {
then:
!data.contains('world')
data.contains('mundo')

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package nextflow.scm
import spock.lang.IgnoreIf
import spock.lang.Requires
import spock.lang.Specification

/**
*
* @author Akira Sekiguchi <[email protected]>
Expand All @@ -28,76 +27,83 @@ class GiteaRepositoryProviderTest extends Specification {

static final String CONFIG = '''
providers {

mygitea {
server = 'https://git.seqera.io'
endpoint = 'https://git.seqera.io/api/v1'
server = 'https://gitea.com'
endpoint = 'https://gitea.com/api/v1'
platform = 'gitea'
user = 'myname'
password = 'mypassword'
}

}
'''

def 'should return repo url' () {

given:
def config = new ConfigSlurper().parse(CONFIG)
def obj = new ProviderConfig('gitea', config.providers.mygitea as ConfigObject)

expect:
new GiteaRepositoryProvider('pditommaso/hello', obj).getEndpointUrl() == 'https://git.seqera.io/api/v1/repos/pditommaso/hello'
new GiteaRepositoryProvider('pditommaso/hello', obj).getEndpointUrl() == 'https://gitea.com/api/v1/repos/pditommaso/hello'
}

def 'should return project URL' () {

given:
def config = new ConfigSlurper().parse(CONFIG)
def obj = new ProviderConfig('gitea', config.providers.mygitea as ConfigObject)

expect:
new GiteaRepositoryProvider('pditommaso/hello', obj).getRepositoryUrl() == 'https://git.seqera.io/pditommaso/hello'

new GiteaRepositoryProvider('pditommaso/hello', obj).getRepositoryUrl() == 'https://gitea.com/pditommaso/hello'
}

def 'should return content URL' () {

given:
def config = new ConfigSlurper().parse(CONFIG)
def obj = new ProviderConfig('gitea', config.providers.mygitea as ConfigObject)

expect:
new GiteaRepositoryProvider('pditommaso/hello', obj)
.getContentUrl('main.nf') == 'https://git.seqera.io/api/v1/repos/pditommaso/hello/raw/main.nf'
.getContentUrl('main.nf') == 'https://gitea.com/api/v1/repos/pditommaso/hello/raw/main.nf'
and:
new GiteaRepositoryProvider('pditommaso/hello', obj)
.setRevision('12345')
.getContentUrl('main.nf') == 'https://git.seqera.io/api/v1/repos/pditommaso/hello/raw/main.nf?ref=12345'
.getContentUrl('main.nf') == 'https://gitea.com/api/v1/repos/pditommaso/hello/raw/main.nf?ref=12345'

}

@IgnoreIf({System.getenv('NXF_SMOKE')})
@Requires({System.getenv('NXF_GITEA_ACCESS_TOKEN')})
def 'should read file content'() {

given:
def token = System.getenv('NXF_GITEA_ACCESS_TOKEN')
def config = new ProviderConfig('gitea', new ConfigSlurper().parse(CONFIG).providers.mygitea as ConfigObject).setAuth(token)
def token = System.getenv('NXF_GITEA_ACCESS_TOKEN')
def config = new ProviderConfig('gitea', new ConfigSlurper().parse(CONFIG).providers.mygitea as ConfigObject) .setAuth(token)

when:
def repo = new GiteaRepositoryProvider('test-org/nextflow-ci-repo', config)
def repo = new GiteaRepositoryProvider('pditommaso/test-hello', config)
def result = repo.readText('README.md')
then:
result.contains('nextflow-ci-repo')
result.contains('Basic Nextflow script')

// when:
// repo = new GiteaRepositoryProvider('test-org/nextflow-ci-repo', config)
// .setRevision('foo')
// result = repo.readText('README.md')
// then:
// result.contains("foo branch")
}

@IgnoreIf({System.getenv('NXF_SMOKE')})
@Requires({System.getenv('NXF_GITEA_ACCESS_TOKEN')})
def 'should read bytes gitea content'() {
given:
def token = System.getenv('NXF_GITEA_ACCESS_TOKEN')
def config = new ProviderConfig('gitea', new ConfigSlurper().parse(CONFIG).providers.mygitea as ConfigObject) .setAuth(token)
def repo = new GiteaRepositoryProvider('pditommaso/test-hello', config)
and:
def DATA = this.class.getResourceAsStream('/test-asset.bin').bytes

when:
def result = repo.readBytes('test/test-asset.bin')

then:
result == DATA
}

}
Loading
Loading