Skip to content

Commit

Permalink
Create a new API to download artifacts from a PipelineRun (#479)
Browse files Browse the repository at this point in the history
* create a new API to download artifacts from a PipelineRun

* Modify the route for downloading artifacts

* fix: using the Jenkins API download artifact
  • Loading branch information
mzmuer authored Mar 18, 2022
1 parent 1493701 commit b726b90
Show file tree
Hide file tree
Showing 12 changed files with 125 additions and 19 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ require (
github.com/google/go-cmp v0.5.5
github.com/google/go-querystring v1.1.0 // indirect
github.com/h2non/gock v1.0.9
github.com/jenkins-zh/jenkins-client v0.0.8
github.com/jenkins-zh/jenkins-client v0.0.9
github.com/kubesphere/sonargo v0.0.2
github.com/onsi/ginkgo v1.16.4
github.com/onsi/gomega v1.15.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -324,8 +324,8 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jenkins-zh/jenkins-cli v0.0.32/go.mod h1:uE1mH9PNITrg0sugv6HXuM/CSddg0zxXoYu3w57I3JY=
github.com/jenkins-zh/jenkins-client v0.0.8 h1:leaVU9pYvWcAhpG4VkX7KUFIcvNp1qxwnnGVjkhSfMU=
github.com/jenkins-zh/jenkins-client v0.0.8/go.mod h1:ICBk7OOoTafVP//f/VfKZ34c0ff8vJwVnOsF9btiMYU=
github.com/jenkins-zh/jenkins-client v0.0.9 h1:yqWtjLifYWbVxR+wFdExh/cHSQ54i/Cdco2MYkc/5vI=
github.com/jenkins-zh/jenkins-client v0.0.9/go.mod h1:ICBk7OOoTafVP//f/VfKZ34c0ff8vJwVnOsF9btiMYU=
github.com/jenkins-zh/jenkins-formulas v0.0.5/go.mod h1:zS8fm8u5L6FcjZM0QznXsLV9T2UtSVK+hT6Sm76iUZ4=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
Expand Down
5 changes: 5 additions & 0 deletions pkg/client/devops/fake/fakedevops.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package fake

import (
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
Expand Down Expand Up @@ -189,6 +190,10 @@ func (d *Devops) RunPipeline(projectName, pipelineName string, httpParameters *d
func (d *Devops) GetArtifacts(projectName, pipelineName, runId string, httpParameters *devops.HttpParameters) ([]devops.Artifacts, error) {
return nil, nil
}
func (d *Devops) DownloadArtifact(projectName, pipelineName, runId, filename string) (io.ReadCloser, error) {
return nil, nil
}

func (d *Devops) GetRunLog(projectName, pipelineName, runId string, httpParameters *devops.HttpParameters) ([]byte, error) {
return nil, nil
}
Expand Down
15 changes: 15 additions & 0 deletions pkg/client/devops/jclient/pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ limitations under the License.
package jclient

import (
"fmt"
"io"
"net/http"
"strconv"

"github.com/jenkins-zh/jenkins-client/pkg/artifact"

"kubesphere.io/devops/pkg/client/devops"
)
Expand Down Expand Up @@ -62,6 +67,16 @@ func (j *JenkinsClient) GetArtifacts(projectName, pipelineName, runID string, ht
return j.jenkins.GetArtifacts(projectName, pipelineName, runID, httpParameters)
}

// DownloadArtifact download an artifact
func (j *JenkinsClient) DownloadArtifact(projectName, pipelineName, runID, filename string) (io.ReadCloser, error) {
jobRunID, err := strconv.Atoi(runID)
if err != nil {
return nil, fmt.Errorf("runId error, not a number: %v", err)
}
c := artifact.Client{JenkinsCore: j.Core}
return c.GetArtifact(projectName, pipelineName, jobRunID, filename)
}

// GetRunLog returns the log output of a pipeline run
func (j *JenkinsClient) GetRunLog(projectName, pipelineName, runID string, httpParameters *devops.HttpParameters) ([]byte, error) {
return j.jenkins.GetRunLog(projectName, pipelineName, runID, httpParameters)
Expand Down
1 change: 1 addition & 0 deletions pkg/client/devops/pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -1135,6 +1135,7 @@ type PipelineOperator interface {
ReplayPipeline(projectName, pipelineName, runId string, httpParameters *HttpParameters) (*ReplayPipeline, error)
RunPipeline(projectName, pipelineName string, httpParameters *HttpParameters) (*RunPipeline, error)
GetArtifacts(projectName, pipelineName, runId string, httpParameters *HttpParameters) ([]Artifacts, error)
DownloadArtifact(projectName, pipelineName, runId, filename string) (io.ReadCloser, error)
GetRunLog(projectName, pipelineName, runId string, httpParameters *HttpParameters) ([]byte, error)
GetStepLog(projectName, pipelineName, runId, nodeId, stepId string, httpParameters *HttpParameters) ([]byte, http.Header, error)
GetNodeSteps(projectName, pipelineName, runId, nodeId string, httpParameters *HttpParameters) ([]NodeSteps, error)
Expand Down
3 changes: 2 additions & 1 deletion pkg/kapis/devops/v1alpha2/devops.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ import (
"encoding/json"
"errors"
"fmt"
"kubesphere.io/devops/pkg/kapis"
"net/http"
"strings"

"kubesphere.io/devops/pkg/kapis"

"kubesphere.io/devops/pkg/apiserver/query"
"kubesphere.io/devops/pkg/apiserver/request"

Expand Down
6 changes: 4 additions & 2 deletions pkg/kapis/devops/v1alpha2/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ package v1alpha2
import (
"context"
"fmt"
"net/url"
"strings"

"github.com/jenkins-zh/jenkins-client/pkg/core"
"kubesphere.io/devops/pkg/apiserver/runtime"
"kubesphere.io/devops/pkg/client/k8s"
"net/url"
"strings"

"github.com/emicklei/go-restful"
restfulspec "github.com/emicklei/go-restful-openapi"
Expand Down Expand Up @@ -725,6 +726,7 @@ func addJenkinsToContainer(webservice *restful.WebService, devopsClient devops.I
To(jenkinsProxy.proxyWithDevOps).
Returns(http.StatusOK, api.StatusOK, nil).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsJenkinsTag}))

return nil
}

Expand Down
7 changes: 4 additions & 3 deletions pkg/kapis/devops/v1alpha2/register_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ package v1alpha2

import (
"context"
"net/http"
"net/http/httptest"
"testing"

"github.com/emicklei/go-restful"
"github.com/jenkins-zh/jenkins-client/pkg/core"
"github.com/stretchr/testify/assert"
Expand All @@ -28,9 +32,6 @@ import (
fakedevops "kubesphere.io/devops/pkg/client/devops/fake"
"kubesphere.io/devops/pkg/client/k8s"
"kubesphere.io/devops/pkg/constants"
"net/http"
"net/http/httptest"
"testing"
)

func TestAPIsExist(t *testing.T) {
Expand Down
62 changes: 60 additions & 2 deletions pkg/kapis/devops/v1alpha3/pipelinerun/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,33 @@ limitations under the License.
package pipelinerun

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"kubesphere.io/devops/pkg/kapis"
"net/url"
"strconv"

"kubesphere.io/devops/pkg/kapis"

"github.com/emicklei/go-restful"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/klog"
"kubesphere.io/devops/pkg/api/devops/v1alpha3"
"kubesphere.io/devops/pkg/apiserver/query"
apiserverrequest "kubesphere.io/devops/pkg/apiserver/request"
"kubesphere.io/devops/pkg/client/devops"
devopsClient "kubesphere.io/devops/pkg/client/devops"
"kubesphere.io/devops/pkg/models/pipelinerun"
resourcesV1alpha3 "kubesphere.io/devops/pkg/models/resources/v1alpha3"
"sigs.k8s.io/controller-runtime/pkg/client"
)

// apiHandlerOption holds some useful tools for API handler.
type apiHandlerOption struct {
client client.Client
devopsClient devopsClient.Interface
client client.Client
}

// apiHandler contains functions to handle coming request and give a response.
Expand Down Expand Up @@ -220,3 +225,56 @@ func (h *apiHandler) getNodeDetails(request *restful.Request, response *restful.

_ = response.WriteEntity(&stages)
}

// downloadArtifact API to download artifacts from Jenkins
func (h *apiHandler) downloadArtifact(request *restful.Request, response *restful.Response) {
namespaceName := request.PathParameter("namespace")
pipelineRunName := request.PathParameter("pipelinerun")
filename := request.QueryParameter("filename")

// get pipelinerun
pr := &v1alpha3.PipelineRun{}
err := h.client.Get(context.Background(), client.ObjectKey{Namespace: namespaceName, Name: pipelineRunName}, pr)
if err != nil {
kapis.HandleError(request, response, err)
return
}

filename, err = url.QueryUnescape(filename)
if err != nil {
kapis.HandleError(request, response, err)
return
}

buildID, exists := pr.GetPipelineRunID()
if !exists {
kapis.HandleError(request, response, fmt.Errorf("unable to get PipelineRun nodes due to not found run ID"))
return
}
pipelineName := pr.Labels[v1alpha3.PipelineNameLabelKey]

// request the Jenkins API to download artifact
body, err := h.devopsClient.DownloadArtifact(namespaceName, pipelineName, buildID, filename)
if err != nil {
kapis.HandleError(request, response, err)
return
}
defer func() {
_ = body.Close()
}()

buf := &bytes.Buffer{}
if _, err = io.Copy(buf, body); err != nil {
kapis.HandleError(request, response, err)
return
}

// add download header
response.AddHeader("Content-Type", "application/octet-stream")
response.AddHeader("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
_, err = response.Write(buf.Bytes())
if err != nil {
kapis.HandleError(request, response, err)
return
}
}
18 changes: 16 additions & 2 deletions pkg/kapis/devops/v1alpha3/pipelinerun/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,24 @@ package pipelinerun
import (
"net/http"

restfulspec "github.com/emicklei/go-restful-openapi"
"kubesphere.io/devops/pkg/constants"

"kubesphere.io/devops/pkg/api/devops/v1alpha3"
"kubesphere.io/devops/pkg/models/pipelinerun"

"github.com/emicklei/go-restful"
"kubesphere.io/devops/pkg/api"
"kubesphere.io/devops/pkg/client/devops"
devopsClient "kubesphere.io/devops/pkg/client/devops"
"sigs.k8s.io/controller-runtime/pkg/client"
)

// RegisterRoutes register routes into web service.
func RegisterRoutes(ws *restful.WebService, c client.Client) {
func RegisterRoutes(ws *restful.WebService, devopsClient devopsClient.Interface, c client.Client) {
handler := newAPIHandler(apiHandlerOption{
client: c,
devopsClient: devopsClient,
client: c,
})

ws.Route(ws.GET("/namespaces/{namespace}/pipelines/{pipeline}/pipelineruns").
Expand Down Expand Up @@ -69,4 +74,13 @@ func RegisterRoutes(ws *restful.WebService, c client.Client) {
Param(ws.PathParameter("namespace", "Namespace of the PipelineRun")).
Param(ws.PathParameter("pipelinerun", "Name of the PipelineRun")).
Returns(http.StatusOK, api.StatusOK, []pipelinerun.NodeDetail{}))

// download PipelineRun artifact
ws.Route(ws.GET("/namespaces/{namespace}/pipelineruns/{pipelinerun}/artifacts/download").
Param(ws.PathParameter("namespace", "Namespace of the PipelineRun")).
Param(ws.PathParameter("pipelinerun", "Name of the PipelineRun")).
Param(ws.QueryParameter("filename", "artifact filename. e.g. artifact:v1.0.1")).
To(handler.downloadArtifact).
Returns(http.StatusOK, api.StatusOK, nil).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}))
}
16 changes: 12 additions & 4 deletions pkg/kapis/devops/v1alpha3/pipelinerun/register_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,16 @@ limitations under the License.
package pipelinerun

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/emicklei/go-restful"
"github.com/stretchr/testify/assert"
"kubesphere.io/devops/pkg/api/devops/v1alpha1"
"kubesphere.io/devops/pkg/apiserver/runtime"
"net/http"
"net/http/httptest"
fakedevops "kubesphere.io/devops/pkg/client/devops/fake"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"testing"
)

func TestAPIsExist(t *testing.T) {
Expand All @@ -34,7 +36,7 @@ func TestAPIsExist(t *testing.T) {
schema, err := v1alpha1.SchemeBuilder.Register().Build()
assert.Nil(t, err)

RegisterRoutes(wsWithGroup, fake.NewFakeClientWithScheme(schema))
RegisterRoutes(wsWithGroup, fakedevops.NewFakeDevops(nil), fake.NewFakeClientWithScheme(schema))
restful.DefaultContainer.Add(wsWithGroup)

type args struct {
Expand Down Expand Up @@ -68,6 +70,12 @@ func TestAPIsExist(t *testing.T) {
method: http.MethodGet,
uri: "/namespaces/fake/pipelineruns/fake/nodedetails",
},
}, {
name: "download artifact",
args: args{
method: http.MethodGet,
uri: "/namespaces/fake/pipelineruns/fake/artifacts/download",
},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
5 changes: 3 additions & 2 deletions pkg/kapis/devops/v1alpha3/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@
package v1alpha3

import (
"net/http"

"kubesphere.io/devops/pkg/kapis/devops/v1alpha3/common"
"kubesphere.io/devops/pkg/kapis/devops/v1alpha3/scm"
"kubesphere.io/devops/pkg/kapis/devops/v1alpha3/template"
"net/http"

restful "github.com/emicklei/go-restful"
restfulspec "github.com/emicklei/go-restful-openapi"
Expand Down Expand Up @@ -56,7 +57,7 @@ func AddToContainer(container *restful.Container, devopsClient devopsClient.Inte

for _, service := range services {
registerRoutes(devopsClient, k8sClient, client, service)
pipelinerun.RegisterRoutes(service, client)
pipelinerun.RegisterRoutes(service, devopsClient, client)
pipeline.RegisterRoutes(service, client)
template.RegisterRoutes(service, &common.Options{
GenericClient: client,
Expand Down

0 comments on commit b726b90

Please sign in to comment.