StreamFile not recognized as method in custom script hook?

DireLogomachist September 1, 2016

I'm trying to implement a pre-receive hook in Bitbucket Server using the Custom Script Hook feature. I'm not the most savvy with Groovy, but I was fairly sure I'd got the syntax right. However, on trying to add the hook it gives the error:

 

[Static type checking] - Cannot find matching method com.atlassian.bitbucket.content.ContentServce#streamFile

 

import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.bitbucket.hook.HookResponse
import com.atlassian.bitbucket.repository.RefChange
import com.atlassian.bitbucket.repository.Repository
import com.atlassian.bitbucket.content.ContentService
import com.atlassian.bitbucket.content.FileContentCallback

if(repository.getProject().getName() == "Constellation" && repository.getName() == "apollo" && refChanges.pathsMatch('glob:apollo/ui/**')) {
    def contentService = ComponentLocator.getComponent(ContentService)
    def filePath = "apollo/ui/dist/js/app.js"
    def searchFragment = "WSURL=window.location.hostname"
    def fragmentFound = false
    contentService.streamFile(repository, refChanges.first().getToHash(), filePath, new FileContentCallback() {
        @Override
        boolean onLine(int lineNumber, String line, boolean truncated) throws IOException {
            if(line.toLowerCase().contains(searchFragment.toLowerCase()))
                fragmentFound = true
            return true;
        }
    });
    if(!fragmentFound) {
        //reject push with note
        hookResponse.out().print("Push rejected!\nAre you sure you ran `npm run build`?\n\n")
        return true
    } else {
        //allow push
        return false
    }
}

 

I've imported ContentService and it does seem to recognize ContentService when obtained with ComponentLocator. So why doesn't it recognize the streamFile method?

 

Any other comments on my code are welcome as well. The purpose of the pre-receive hook is to only accept pushes that have edits to files in a certain folder if a specific file has a certain substring. The API seems to say that ContentService.streamFile is the correct method to read a whole source file, as opposed to just reading changes from a given commit.

 

Thanks in advance!

2 answers

1 accepted

Comments for this post are closed

Community moderators have prevented the ability to post new answers.

Post a new question

2 votes
Answer accepted
adammarkham
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
September 1, 2016

Theres a siimilar question here regarding reading files in a repository using ScriptRunner: https://answers.atlassian.com/questions/39450211

Your code should look like this:

import com.atlassian.bitbucket.content.ContentService
import com.atlassian.bitbucket.io.TypeAwareOutputSupplier
import com.atlassian.sal.api.component.ComponentLocator

import javax.annotation.Nonnull
import java.nio.charset.StandardCharsets

if(repository.getProject().getName() == "Constellation" && repository.getName() == "apollo" && refChanges.pathsMatch('glob:apollo/ui/**')) {
    def contentService = ComponentLocator.getComponent(ContentService)
    def filePath = "apollo/ui/dist/js/app.js"
    def searchFragment = "WSURL=window.location.hostname"

    def supplier = new ByteArrayStreamOutputSupplier()

    contentService.streamFile(repository, refChanges.first().getToHash(), filePath, supplier)

    supplier.stream.close()

    // convert byte array to UTF-8 encoded string which has the file contents
    def content = new String(supplier.baos.toByteArray(), StandardCharsets.UTF_8)

    // process each line in the content
    def fragmentFound = content.readLines().any { line ->
        line.toLowerCase().contains(searchFragment.toLowerCase())
    }

    if(!fragmentFound) {
        //reject push with note
        hookResponse.out().print("Push rejected!\nAre you sure you ran `npm run build`?\n\n")
        return false
    }
}

return true

class ByteArrayStreamOutputSupplier implements TypeAwareOutputSupplier {

    ByteArrayOutputStream baos
    BufferedOutputStream stream

    ByteArrayStreamOutputSupplier() {
        this.baos = new ByteArrayOutputStream()
        this.stream = new BufferedOutputStream(baos)
    }

    @Override
    OutputStream getStream(@Nonnull String contentType) throws IOException {
        stream
    }
}

Issues I can see are:

  • You need to return false if you want to block the push and true if you want to allow the push.
  • ContentService looks like its spelt wrong in that error message, could that be the issue?
  • Your best off using the TypeAwareOutputSupplier version of stream file, its just easier to use in my view.
  • May be best off using repository.getSlug() and project.getKey() rather than the names.

You'll see a static type checking error in the inline code editor but its safe to ignore, it just can't work out where the pathsMatch method comes from, which is one ScriptRunner provides.

For future reference if you tag your question with: com.onresolve.jira.stash.groovyrunner its easier for us to respond and means your question will be found by the right person.

0 votes
DireLogomachist September 2, 2016

Thanks for the response! I'll try out your changes and see if they work. The misspelled error message was my bad, not related to the code.

TAGS
AUG Leaders

Atlassian Community Events