Merge branch 'job_service' of https://github.com/vmware/harbor into job_service

This commit is contained in:
Steven Zou 2018-03-28 11:26:36 +08:00
commit 4541d4b53f
8 changed files with 128 additions and 22 deletions

View File

@ -16,7 +16,7 @@ import (
type Client interface { type Client interface {
SubmitJob(*models.JobData) (string, error) SubmitJob(*models.JobData) (string, error)
GetJobLog(uuid string) ([]byte, error) GetJobLog(uuid string) ([]byte, error)
//TODO actions or stop? //TODO actions or stop? Redirect joblog when we see there's memory issue.
} }
// DefaultClient is the default implementation of Client interface // DefaultClient is the default implementation of Client interface
@ -101,5 +101,3 @@ func (d *DefaultClient) GetJobLog(uuid string) ([]byte, error) {
} }
return data, nil return data, nil
} }
//TODO: builder, default client, etc.

View File

@ -3,12 +3,32 @@ package job
const ( const (
//ImageScanJob is name of scan job it will be used as key to register to job service. //ImageScanJob is name of scan job it will be used as key to register to job service.
ImageScanJob = "IMAGE_SCAN" ImageScanJob = "IMAGE_SCAN"
// GenericKind marks the job as a generic job, it will be contained in job metadata and passed to job service.
GenericKind = "Generic"
// ImageTransfer : the name of image transfer job in job service // ImageTransfer : the name of image transfer job in job service
ImageTransfer = "IMAGE_TRANSFER" ImageTransfer = "IMAGE_TRANSFER"
// ImageDelete : the name of image delete job in job service // ImageDelete : the name of image delete job in job service
ImageDelete = "IMAGE_DELETE" ImageDelete = "IMAGE_DELETE"
// ImageReplicate : the name of image replicate job in job service // ImageReplicate : the name of image replicate job in job service
ImageReplicate = "IMAGE_REPLICATE" ImageReplicate = "IMAGE_REPLICATE"
//JobKindGeneric : Kind of generic job
JobKindGeneric = "Generic"
//JobKindScheduled : Kind of scheduled job
JobKindScheduled = "Scheduled"
//JobKindPeriodic : Kind of periodic job
JobKindPeriodic = "Periodic"
//JobServiceStatusPending : job status pending
JobServiceStatusPending = "Pending"
//JobServiceStatusRunning : job status running
JobServiceStatusRunning = "Running"
//JobServiceStatusStopped : job status stopped
JobServiceStatusStopped = "Stopped"
//JobServiceStatusCancelled : job status cancelled
JobServiceStatusCancelled = "Cancelled"
//JobServiceStatusError : job status error
JobServiceStatusError = "Error"
//JobServiceStatusSuccess : job status success
JobServiceStatusSuccess = "Success"
//JobServiceStatusScheduled : job status scheduled
JobServiceStatusScheduled = "Scheduled"
) )

View File

@ -51,8 +51,7 @@ func (cj *ClairJob) Validate(params map[string]interface{}) error {
// Run implements the interface in job/Interface // Run implements the interface in job/Interface
func (cj *ClairJob) Run(ctx env.JobContext, params map[string]interface{}) error { func (cj *ClairJob) Run(ctx env.JobContext, params map[string]interface{}) error {
// TODO: get logger from ctx? logger := ctx.GetLogger()
logger := log.DefaultLogger()
jobParms, err := transformParam(params) jobParms, err := transformParam(params)
if err != nil { if err != nil {
@ -79,7 +78,11 @@ func (cj *ClairJob) Run(ctx env.JobContext, params map[string]interface{}) error
logger.Errorf("Failed to prepare layers, error: %v", err) logger.Errorf("Failed to prepare layers, error: %v", err)
return err return err
} }
clairClient := clair.NewClient(jobParms.ClairEndpoint, logger) loggerImpl, ok := logger.(*log.Logger)
if !ok {
loggerImpl = log.DefaultLogger()
}
clairClient := clair.NewClient(jobParms.ClairEndpoint, loggerImpl)
for _, l := range layers { for _, l := range layers {
logger.Infof("Scanning Layer: %s, path: %s", l.Name, l.Path) logger.Infof("Scanning Layer: %s, path: %s", l.Name, l.Path)

View File

@ -19,6 +19,7 @@ import (
"github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/ui/utils" "github.com/vmware/harbor/src/ui/utils"
"fmt"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
@ -29,6 +30,7 @@ type ScanJobAPI struct {
BaseController BaseController
jobID int64 jobID int64
projectName string projectName string
jobUUID string
} }
// Prepare validates that whether user has read permission to the project of the repo the scan job scanned. // Prepare validates that whether user has read permission to the project of the repo the scan job scanned.
@ -55,14 +57,21 @@ func (sj *ScanJobAPI) Prepare() {
sj.HandleForbidden(sj.SecurityCtx.GetUsername()) sj.HandleForbidden(sj.SecurityCtx.GetUsername())
} }
sj.projectName = projectName sj.projectName = projectName
sj.jobUUID = data.UUID
} }
//GetLog ... //GetLog ...
func (sj *ScanJobAPI) GetLog() { func (sj *ScanJobAPI) GetLog() {
url := buildJobLogURL(strconv.FormatInt(sj.jobID, 10), ScanJobType) logBytes, err := utils.GetJobServiceClient().GetJobLog(sj.jobUUID)
err := utils.RequestAsUI(http.MethodGet, url, nil, utils.NewJobLogRespHandler(&sj.BaseAPI))
if err != nil { if err != nil {
sj.RenderError(http.StatusInternalServerError, err.Error()) sj.HandleInternalServerError(fmt.Sprintf("Failed to get job logs, uuid: %s, error: %v", sj.jobUUID, err))
return return
} }
sj.Ctx.ResponseWriter.Header().Set(http.CanonicalHeaderKey("Content-Length"), strconv.Itoa(len(logBytes)))
sj.Ctx.ResponseWriter.Header().Set(http.CanonicalHeaderKey("Content-Type"), "text/plain")
_, err = sj.Ctx.ResponseWriter.Write(logBytes)
if err != nil {
sj.HandleInternalServerError(fmt.Sprintf("Failed to write job logs, uuid: %s, error: %v", sj.jobUUID, err))
}
} }

View File

@ -304,17 +304,20 @@ func InternalJobServiceURL() string {
return strings.TrimSuffix(cfg[common.JobServiceURL].(string), "/") return strings.TrimSuffix(cfg[common.JobServiceURL].(string), "/")
} }
// InternalTokenServiceEndpoint returns token service endpoint for internal communication between Harbor containers // InternalUIURL returns the local ui url
func InternalTokenServiceEndpoint() string { func InternalUIURL() string {
uiURL := common.DefaultUIEndpoint
cfg, err := mg.Get() cfg, err := mg.Get()
if err != nil { if err != nil {
log.Warningf("Failed to Get job service UI URL from backend, error: %v, will use default value.") log.Warningf("Failed to Get job service UI URL from backend, error: %v, will return default value.")
return common.DefaultUIEndpoint
} else {
uiURL = cfg[common.UIURL].(string)
} }
return strings.TrimSuffix(uiURL, "/") + "/service/token" return strings.TrimSuffix(cfg[common.UIURL].(string), "/")
}
// InternalTokenServiceEndpoint returns token service endpoint for internal communication between Harbor containers
func InternalTokenServiceEndpoint() string {
return InternalUIURL() + "/service/token"
} }
// InternalNotaryEndpoint returns notary server endpoint for internal communication between Harbor containers // InternalNotaryEndpoint returns notary server endpoint for internal communication between Harbor containers

View File

@ -19,6 +19,7 @@ import (
"github.com/vmware/harbor/src/ui/config" "github.com/vmware/harbor/src/ui/config"
"github.com/vmware/harbor/src/ui/controllers" "github.com/vmware/harbor/src/ui/controllers"
"github.com/vmware/harbor/src/ui/service/notifications/clair" "github.com/vmware/harbor/src/ui/service/notifications/clair"
"github.com/vmware/harbor/src/ui/service/notifications/jobs"
"github.com/vmware/harbor/src/ui/service/notifications/registry" "github.com/vmware/harbor/src/ui/service/notifications/registry"
"github.com/vmware/harbor/src/ui/service/token" "github.com/vmware/harbor/src/ui/service/token"
@ -112,10 +113,12 @@ func initRouters() {
//external service that hosted on harbor process: //external service that hosted on harbor process:
beego.Router("/service/notifications", &registry.NotificationHandler{}) beego.Router("/service/notifications", &registry.NotificationHandler{})
beego.Router("/service/notifications/clair", &clair.Handler{}, "post:Handle") beego.Router("/service/notifications/clair", &clair.Handler{}, "post:Handle")
beego.Router("/service/notifications/jobs/scan/:id([0-9]+)", &jobs.Handler{}, "post:HandleScan")
beego.Router("/service/notifications/jobs/replication/:id([0-9]+)", &jobs.Handler{}, "post:HandleReplication")
beego.Router("/service/token", &token.Handler{}) beego.Router("/service/token", &token.Handler{})
beego.Router("/registryproxy/*", &controllers.RegistryProxy{}, "*:Handle") beego.Router("/registryproxy/*", &controllers.RegistryProxy{}, "*:Handle")
//Error pages //Error pages
beego.ErrorController(&controllers.ErrorController{}) beego.ErrorController(&controllers.ErrorController{})

View File

@ -0,0 +1,69 @@
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
//
// 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 jobs
import (
"encoding/json"
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/job"
jobmodels "github.com/vmware/harbor/src/common/job/models"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/ui/api"
)
var statusMap = map[string]string{
job.JobServiceStatusPending: models.JobPending,
job.JobServiceStatusRunning: models.JobRunning,
job.JobServiceStatusStopped: models.JobStopped,
job.JobServiceStatusCancelled: models.JobCanceled,
job.JobServiceStatusError: models.JobError,
job.JobServiceStatusSuccess: models.JobFinished,
}
// Handler handles reqeust on /service/notifications/jobs/*, which listens to the webhook of jobservice.
type Handler struct {
api.BaseController
}
// HandleScan handles the webhook of scan job
func (h *Handler) HandleScan() {
id, err := h.GetInt64FromPath(":id")
if err != nil {
log.Errorf("Failed to get job ID, error: %v", err)
//Avoid job service from resending...
return
}
var data jobmodels.JobStatusChange
err = json.Unmarshal(h.Ctx.Input.CopyBody(1<<32), &data)
if err != nil {
log.Errorf("Failed to decode job status change, job ID: %d, error: %v", id, err)
return
}
status, ok := statusMap[data.Status]
log.Debugf("Received scan job status update for job: %d, status: %s", id, data.Status)
if ok {
if err := dao.UpdateScanJobStatus(id, status); err != nil {
log.Errorf("Failed to update job status, id: %d, data: %v", id, data)
h.HandleInternalServerError(err.Error())
}
}
}
//HandleReplication handles the webhook of replication job
func (h *Handler) HandleReplication() {
}

View File

@ -25,6 +25,7 @@ import (
"github.com/vmware/harbor/src/ui/config" "github.com/vmware/harbor/src/ui/config"
"encoding/json" "encoding/json"
"fmt"
"sync" "sync"
) )
@ -164,7 +165,7 @@ func buildScanJobData(jobID int64, repository, tag, digest string) (*jobmodels.J
return nil, err return nil, err
} }
meta := jobmodels.JobMetadata{ meta := jobmodels.JobMetadata{
JobKind: job.GenericKind, JobKind: job.JobKindGeneric,
IsUnique: false, IsUnique: false,
} }
@ -172,7 +173,7 @@ func buildScanJobData(jobID int64, repository, tag, digest string) (*jobmodels.J
Name: job.ImageScanJob, Name: job.ImageScanJob,
Parameters: jobmodels.Parameters(parmsMap), Parameters: jobmodels.Parameters(parmsMap),
Metadata: &meta, Metadata: &meta,
StatusHook: "", StatusHook: fmt.Sprintf("%s/service/notifications/jobs/scan/%d", config.InternalUIURL(), jobID),
} }
return data, nil return data, nil