mirror of
https://gitea.com/gitea/runner.git
synced 2026-11-04 22:37:55 +00:00
* Add asset server and upload handling of binary files Co-authored-by: Björn Brauer <[email protected]> * Add asset download parts to the asset server Co-authored-by: Björn Brauer <[email protected]> * Add artifact-server-path flag If the flag is not defined, the artifact server isn't started. This includes the configuration of ACTIONS_RUNTIME_URL and ACTIONS_RUNTIME_TOKEN which are set if the server is started. Co-authored-by: Björn Brauer <[email protected]> * Move ACTIONS_RUNTIME_* vars into the withGithubEnv setup Co-authored-by: Björn Brauer <[email protected]> * feat: add artifact server port as flag This commits adds a flag to define the artifact server port. If not given, the port defaults to 34567. Co-authored-by: Björn Brauer <[email protected]> * test: add artifact server tests Co-authored-by: Björn Brauer <[email protected]> * refactor: use fs.FS This allows to add tests with in-memory file system * feat: add support for gzip encoded uploads Co-authored-by: Björn Brauer <[email protected]> * test: add artifact integration test * chore: run act tests with asset server path Co-authored-by: Björn Brauer <[email protected]> * docs: add new cli flags Co-authored-by: Björn Brauer <[email protected]> * test: add test workflow to testdata * feat: add log output * refactor: log shutdown error instead of panic * feat: use outbound ip for the asset server This change should allow to use the host ip in macos and windows. Since docker is running in an intermediate vm, localhost is not sufficient to have the artifacts in the host system. * fix: do not use canceled context To shutdown artifact server, we should not use the already canceled context but the parent context instead. Co-authored-by: Björn Brauer <[email protected]> * feat: shutdown artifact server at end of pipeline When the pipeline is done the asset server should be shut down gracefully. Co-authored-by: Björn Brauer <[email protected]> * fix: close server if graceful shutdown failed Co-authored-by: Björn Brauer <[email protected]> * fix: ignore server closed error from listen call Co-authored-by: Björn Brauer <[email protected]> Co-authored-by: Björn Brauer <[email protected]> Co-authored-by: Björn Brauer <[email protected]>
297 lines
6.8 KiB
Go
297 lines
6.8 KiB
Go
package artifacts
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/fs"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"testing/fstest"
|
|
|
|
"github.com/julienschmidt/httprouter"
|
|
"github.com/nektos/act/pkg/model"
|
|
"github.com/nektos/act/pkg/runner"
|
|
log "github.com/sirupsen/logrus"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
type MapFsImpl struct {
|
|
fstest.MapFS
|
|
}
|
|
|
|
func (fsys MapFsImpl) MkdirAll(path string, perm fs.FileMode) error {
|
|
// mocked no-op
|
|
return nil
|
|
}
|
|
|
|
type WritableFile struct {
|
|
fs.File
|
|
fsys fstest.MapFS
|
|
path string
|
|
}
|
|
|
|
func (file WritableFile) Write(data []byte) (int, error) {
|
|
file.fsys[file.path].Data = data
|
|
return len(data), nil
|
|
}
|
|
|
|
func (fsys MapFsImpl) Open(path string) (fs.File, error) {
|
|
var file = fstest.MapFile{
|
|
Data: []byte("content2"),
|
|
}
|
|
fsys.MapFS[path] = &file
|
|
|
|
result, err := fsys.MapFS.Open(path)
|
|
return WritableFile{result, fsys.MapFS, path}, err
|
|
}
|
|
|
|
func TestNewArtifactUploadPrepare(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
var memfs = fstest.MapFS(map[string]*fstest.MapFile{})
|
|
|
|
router := httprouter.New()
|
|
uploads(router, MapFsImpl{memfs})
|
|
|
|
req, _ := http.NewRequest("POST", "http://localhost/_apis/pipelines/workflows/1/artifacts", nil)
|
|
rr := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(rr, req)
|
|
|
|
if status := rr.Code; status != http.StatusOK {
|
|
assert.Fail("Wrong status")
|
|
}
|
|
|
|
response := FileContainerResourceURL{}
|
|
err := json.Unmarshal(rr.Body.Bytes(), &response)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
assert.Equal("http://localhost/upload/1", response.FileContainerResourceURL)
|
|
}
|
|
|
|
func TestArtifactUploadBlob(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
var memfs = fstest.MapFS(map[string]*fstest.MapFile{})
|
|
|
|
router := httprouter.New()
|
|
uploads(router, MapFsImpl{memfs})
|
|
|
|
req, _ := http.NewRequest("PUT", "http://localhost/upload/1?itemPath=some/file", strings.NewReader("content"))
|
|
rr := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(rr, req)
|
|
|
|
if status := rr.Code; status != http.StatusOK {
|
|
assert.Fail("Wrong status")
|
|
}
|
|
|
|
response := ResponseMessage{}
|
|
err := json.Unmarshal(rr.Body.Bytes(), &response)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
assert.Equal("success", response.Message)
|
|
assert.Equal("content", string(memfs["1/some/file"].Data))
|
|
}
|
|
|
|
func TestFinalizeArtifactUpload(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
var memfs = fstest.MapFS(map[string]*fstest.MapFile{})
|
|
|
|
router := httprouter.New()
|
|
uploads(router, MapFsImpl{memfs})
|
|
|
|
req, _ := http.NewRequest("PATCH", "http://localhost/_apis/pipelines/workflows/1/artifacts", nil)
|
|
rr := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(rr, req)
|
|
|
|
if status := rr.Code; status != http.StatusOK {
|
|
assert.Fail("Wrong status")
|
|
}
|
|
|
|
response := ResponseMessage{}
|
|
err := json.Unmarshal(rr.Body.Bytes(), &response)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
assert.Equal("success", response.Message)
|
|
}
|
|
|
|
func TestListArtifacts(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
var memfs = fstest.MapFS(map[string]*fstest.MapFile{
|
|
"1/file.txt": {
|
|
Data: []byte(""),
|
|
},
|
|
})
|
|
|
|
router := httprouter.New()
|
|
downloads(router, memfs)
|
|
|
|
req, _ := http.NewRequest("GET", "http://localhost/_apis/pipelines/workflows/1/artifacts", nil)
|
|
rr := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(rr, req)
|
|
|
|
if status := rr.Code; status != http.StatusOK {
|
|
assert.FailNow(fmt.Sprintf("Wrong status: %d", status))
|
|
}
|
|
|
|
response := NamedFileContainerResourceURLResponse{}
|
|
err := json.Unmarshal(rr.Body.Bytes(), &response)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
assert.Equal(1, response.Count)
|
|
assert.Equal("file.txt", response.Value[0].Name)
|
|
assert.Equal("http://localhost/download/1", response.Value[0].FileContainerResourceURL)
|
|
}
|
|
|
|
func TestListArtifactContainer(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
var memfs = fstest.MapFS(map[string]*fstest.MapFile{
|
|
"1/some/file": {
|
|
Data: []byte(""),
|
|
},
|
|
})
|
|
|
|
router := httprouter.New()
|
|
downloads(router, memfs)
|
|
|
|
req, _ := http.NewRequest("GET", "http://localhost/download/1?itemPath=some/file", nil)
|
|
rr := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(rr, req)
|
|
|
|
if status := rr.Code; status != http.StatusOK {
|
|
assert.FailNow(fmt.Sprintf("Wrong status: %d", status))
|
|
}
|
|
|
|
response := ContainerItemResponse{}
|
|
err := json.Unmarshal(rr.Body.Bytes(), &response)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
assert.Equal(1, len(response.Value))
|
|
assert.Equal("some/file/.", response.Value[0].Path)
|
|
assert.Equal("file", response.Value[0].ItemType)
|
|
assert.Equal("http://localhost/artifact/1/some/file/.", response.Value[0].ContentLocation)
|
|
}
|
|
|
|
func TestDownloadArtifactFile(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
var memfs = fstest.MapFS(map[string]*fstest.MapFile{
|
|
"1/some/file": {
|
|
Data: []byte("content"),
|
|
},
|
|
})
|
|
|
|
router := httprouter.New()
|
|
downloads(router, memfs)
|
|
|
|
req, _ := http.NewRequest("GET", "http://localhost/artifact/1/some/file", nil)
|
|
rr := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(rr, req)
|
|
|
|
if status := rr.Code; status != http.StatusOK {
|
|
assert.FailNow(fmt.Sprintf("Wrong status: %d", status))
|
|
}
|
|
|
|
data := rr.Body.Bytes()
|
|
|
|
assert.Equal("content", string(data))
|
|
}
|
|
|
|
type TestJobFileInfo struct {
|
|
workdir string
|
|
workflowPath string
|
|
eventName string
|
|
errorMessage string
|
|
platforms map[string]string
|
|
containerArchitecture string
|
|
}
|
|
|
|
var aritfactsPath = path.Join(os.TempDir(), "test-artifacts")
|
|
var artifactsPort = "12345"
|
|
|
|
func TestArtifactFlow(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
cancel := Serve(ctx, aritfactsPath, artifactsPort)
|
|
defer cancel()
|
|
|
|
platforms := map[string]string{
|
|
"ubuntu-latest": "node:12.20.1-buster-slim",
|
|
}
|
|
|
|
tables := []TestJobFileInfo{
|
|
{"testdata", "upload-and-download", "push", "", platforms, ""},
|
|
}
|
|
log.SetLevel(log.DebugLevel)
|
|
|
|
for _, table := range tables {
|
|
runTestJobFile(ctx, t, table)
|
|
}
|
|
}
|
|
|
|
func runTestJobFile(ctx context.Context, t *testing.T, tjfi TestJobFileInfo) {
|
|
t.Run(tjfi.workflowPath, func(t *testing.T) {
|
|
if err := os.RemoveAll(aritfactsPath); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
workdir, err := filepath.Abs(tjfi.workdir)
|
|
assert.Nil(t, err, workdir)
|
|
fullWorkflowPath := filepath.Join(workdir, tjfi.workflowPath)
|
|
runnerConfig := &runner.Config{
|
|
Workdir: workdir,
|
|
BindWorkdir: false,
|
|
EventName: tjfi.eventName,
|
|
Platforms: tjfi.platforms,
|
|
ReuseContainers: false,
|
|
ContainerArchitecture: tjfi.containerArchitecture,
|
|
GitHubInstance: "github.com",
|
|
ArtifactServerPath: aritfactsPath,
|
|
ArtifactServerPort: artifactsPort,
|
|
}
|
|
|
|
runner, err := runner.New(runnerConfig)
|
|
assert.Nil(t, err, tjfi.workflowPath)
|
|
|
|
planner, err := model.NewWorkflowPlanner(fullWorkflowPath, true)
|
|
assert.Nil(t, err, fullWorkflowPath)
|
|
|
|
plan := planner.PlanEvent(tjfi.eventName)
|
|
|
|
err = runner.NewPlanExecutor(plan)(ctx)
|
|
if tjfi.errorMessage == "" {
|
|
assert.Nil(t, err, fullWorkflowPath)
|
|
} else {
|
|
assert.Error(t, err, tjfi.errorMessage)
|
|
}
|
|
})
|
|
}
|