300 lines
10 KiB
Go
300 lines
10 KiB
Go
package service
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"time"
|
|
|
|
"browser.local/platform/domain"
|
|
"browser.local/platform/repo"
|
|
"browser.local/platform/validator"
|
|
)
|
|
|
|
func (svc *CoreService) GetDistributionBuildInput(request domain.DistributionBuildInputRequest) (domain.DistributionBuildInput, error) {
|
|
if err := validator.ValidateDistributionBuildInputRequest(request); err != nil {
|
|
return domain.DistributionBuildInput{}, err
|
|
}
|
|
session, err := svc.validatedRunSession(request.RunEndpointID, request.SessionToken)
|
|
if err != nil {
|
|
return domain.DistributionBuildInput{}, err
|
|
}
|
|
|
|
svc.jobMu.Lock()
|
|
job, err := svc.fencedJob(session, request.JobID, request.LeaseToken, request.Attempt)
|
|
svc.jobMu.Unlock()
|
|
if err != nil {
|
|
return domain.DistributionBuildInput{}, err
|
|
}
|
|
if job.Capability != domain.JobCapabilityDistributionBuild {
|
|
return domain.DistributionBuildInput{}, validationError("job is not a distribution build")
|
|
}
|
|
if job.State != domain.JobStateAccepted && job.State != domain.JobStateRunning {
|
|
return domain.DistributionBuildInput{}, validationError("distribution build job is not active")
|
|
}
|
|
|
|
runDistributions, err := svc.store.RunDistributions().List(domain.RunDistributionFilter{ServerInstanceID: job.ServerInstanceID})
|
|
if err != nil {
|
|
return domain.DistributionBuildInput{}, err
|
|
}
|
|
for _, distribution := range runDistributions {
|
|
if distribution.BuildJobID != job.ID {
|
|
continue
|
|
}
|
|
key, err := svc.activeComponentKey(distribution.ServerInstanceID, domain.DistributionComponentRun, "")
|
|
if err != nil {
|
|
return domain.DistributionBuildInput{}, err
|
|
}
|
|
if key.Generation != distribution.KeyGeneration {
|
|
return domain.DistributionBuildInput{}, validationError("run build key generation is no longer current")
|
|
}
|
|
plainKey, err := svc.decryptRuntimeKey(key.EncryptedKey)
|
|
if err != nil {
|
|
return domain.DistributionBuildInput{}, err
|
|
}
|
|
return domain.DistributionBuildInput{
|
|
JobID: job.ID,
|
|
ComponentKind: domain.DistributionComponentRun,
|
|
ServerInstanceID: distribution.ServerInstanceID,
|
|
PluginID: distribution.PluginID,
|
|
RunEndpointID: distribution.RunEndpointID,
|
|
TargetOS: distribution.TargetOS,
|
|
TargetArch: distribution.TargetArch,
|
|
TargetRelease: distribution.ID,
|
|
PlatformURL: runReleasePlatformURL(),
|
|
PackageFormat: distribution.PackageFormat,
|
|
ArtifactID: distribution.ArtifactID,
|
|
OutputFilename: executableFilename("run", distribution.TargetOS),
|
|
SecretRef: distribution.SecretRef,
|
|
KeyGeneration: distribution.KeyGeneration,
|
|
AuthKey: plainKey,
|
|
}, nil
|
|
}
|
|
|
|
clientDistributions, err := svc.store.ClientManagerDistributions().List(domain.ClientManagerDistributionFilter{ServerInstanceID: job.ServerInstanceID})
|
|
if err != nil {
|
|
return domain.DistributionBuildInput{}, err
|
|
}
|
|
for _, distribution := range clientDistributions {
|
|
if distribution.BuildJobID != job.ID {
|
|
continue
|
|
}
|
|
key, err := svc.activeComponentKey(distribution.ServerInstanceID, domain.DistributionComponentClientManager, distribution.ProfileKey)
|
|
if err != nil {
|
|
return domain.DistributionBuildInput{}, err
|
|
}
|
|
if key.Generation != distribution.KeyGeneration {
|
|
return domain.DistributionBuildInput{}, validationError("client-manager build key generation is no longer current")
|
|
}
|
|
plainKey, err := svc.decryptRuntimeKey(key.EncryptedKey)
|
|
if err != nil {
|
|
return domain.DistributionBuildInput{}, err
|
|
}
|
|
return domain.DistributionBuildInput{
|
|
JobID: job.ID,
|
|
ComponentKind: domain.DistributionComponentClientManager,
|
|
ServerInstanceID: distribution.ServerInstanceID,
|
|
PluginID: distribution.PluginID,
|
|
RunEndpointID: job.RunEndpointID,
|
|
ProfileKey: distribution.ProfileKey,
|
|
TargetOS: distribution.TargetOS,
|
|
TargetArch: distribution.TargetArch,
|
|
PackageFormat: packageFormatForTarget(distribution.TargetOS),
|
|
RepositoryURL: distribution.RepositoryURL,
|
|
SourceRevision: distribution.SourceRevision,
|
|
ArtifactID: distribution.ArtifactID,
|
|
OutputFilename: clientManagerOutputName(distribution.ProfileKey, distribution.TargetOS),
|
|
SecretRef: distribution.SecretRef,
|
|
KeyGeneration: distribution.KeyGeneration,
|
|
AuthKey: plainKey,
|
|
}, nil
|
|
}
|
|
return domain.DistributionBuildInput{}, repo.ErrNotFound
|
|
}
|
|
|
|
func (svc *CoreService) projectDistributionBuildProgress(job domain.Job, stamp time.Time) error {
|
|
if job.Capability != domain.JobCapabilityDistributionBuild {
|
|
return nil
|
|
}
|
|
builds, err := svc.store.ClientManagerBuildJobs().List(domain.ClientManagerBuildJobFilter{ServerInstanceID: job.ServerInstanceID})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, build := range builds {
|
|
if build.ID != job.ID || build.Status != domain.DistributionJobStatusQueued {
|
|
continue
|
|
}
|
|
build.Status = domain.DistributionJobStatusRunning
|
|
build.UpdatedAt = stamp
|
|
if err := validator.ValidateClientManagerBuildJob(build); err != nil {
|
|
return err
|
|
}
|
|
return svc.store.ClientManagerBuildJobs().Update(build)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (svc *CoreService) projectDistributionBuildResult(job domain.Job, stamp time.Time) error {
|
|
if job.Capability != domain.JobCapabilityDistributionBuild {
|
|
return nil
|
|
}
|
|
if err := svc.validateDistributionBuildResult(job); err != nil {
|
|
return err
|
|
}
|
|
status := domain.DistributionStatusFailed
|
|
buildStatus := domain.DistributionJobStatusFailed
|
|
var artifact domain.Artifact
|
|
if job.State == domain.JobStateSucceeded {
|
|
artifactID := strings.TrimPrefix(job.ResultRef, "artifact://")
|
|
if artifactID == "" || artifactID == job.ResultRef {
|
|
return validationError("distribution build result must reference an artifact")
|
|
}
|
|
var err error
|
|
artifact, err = svc.store.Artifacts().Get(artifactID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if artifact.State != domain.ArtifactStateAvailable || artifact.OwnerKind != domain.ArtifactOwnerKindJob || artifact.OwnerID != job.ID {
|
|
return validationError("distribution build artifact is unavailable or outside the job scope")
|
|
}
|
|
status = domain.DistributionStatusAvailable
|
|
buildStatus = domain.DistributionJobStatusSucceeded
|
|
}
|
|
|
|
runDistributions, err := svc.store.RunDistributions().List(domain.RunDistributionFilter{ServerInstanceID: job.ServerInstanceID})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, distribution := range runDistributions {
|
|
if distribution.BuildJobID != job.ID {
|
|
continue
|
|
}
|
|
if distribution.Status == domain.DistributionStatusRevoked {
|
|
if artifact.ID != "" {
|
|
return svc.expireDistributionArtifact(artifact.ID)
|
|
}
|
|
return nil
|
|
}
|
|
if job.State == domain.JobStateSucceeded && artifact.ID != distribution.ArtifactID {
|
|
return validationError("distribution build returned an unexpected artifact")
|
|
}
|
|
distribution.Status = status
|
|
if artifact.ID != "" {
|
|
distribution.Checksum = artifact.Checksum
|
|
}
|
|
distribution.UpdatedAt = stamp
|
|
if err := validator.ValidateRunDistribution(distribution); err != nil {
|
|
return err
|
|
}
|
|
return svc.store.RunDistributions().Update(distribution)
|
|
}
|
|
|
|
clientDistributions, err := svc.store.ClientManagerDistributions().List(domain.ClientManagerDistributionFilter{ServerInstanceID: job.ServerInstanceID})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, distribution := range clientDistributions {
|
|
if distribution.BuildJobID != job.ID {
|
|
continue
|
|
}
|
|
if distribution.Status == domain.DistributionStatusRevoked {
|
|
if artifact.ID != "" {
|
|
return svc.expireDistributionArtifact(artifact.ID)
|
|
}
|
|
return nil
|
|
}
|
|
if job.State == domain.JobStateSucceeded && artifact.ID != distribution.ArtifactID {
|
|
return validationError("client-manager build returned an unexpected artifact")
|
|
}
|
|
distribution.Status = status
|
|
if artifact.ID != "" {
|
|
distribution.Checksum = artifact.Checksum
|
|
}
|
|
distribution.UpdatedAt = stamp
|
|
if err := validator.ValidateClientManagerDistribution(distribution); err != nil {
|
|
return err
|
|
}
|
|
if err := svc.store.ClientManagerDistributions().Update(distribution); err != nil {
|
|
return err
|
|
}
|
|
if err := svc.ProjectClientManagerDistribution(distribution); err != nil {
|
|
return err
|
|
}
|
|
build, err := svc.store.ClientManagerBuildJobs().Get(job.ID)
|
|
if err != nil && !errors.Is(err, repo.ErrNotFound) {
|
|
return err
|
|
}
|
|
if err == nil {
|
|
build.Status = buildStatus
|
|
if artifact.ID != "" {
|
|
build.ArtifactID = artifact.ID
|
|
build.Checksum = artifact.Checksum
|
|
}
|
|
build.UpdatedAt = stamp
|
|
if err := validator.ValidateClientManagerBuildJob(build); err != nil {
|
|
return err
|
|
}
|
|
if err := svc.store.ClientManagerBuildJobs().Update(build); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
return repo.ErrNotFound
|
|
}
|
|
|
|
func (svc *CoreService) validateDistributionBuildResult(job domain.Job) error {
|
|
if job.Capability != domain.JobCapabilityDistributionBuild {
|
|
return nil
|
|
}
|
|
|
|
expectedArtifactID := ""
|
|
runDistributions, err := svc.store.RunDistributions().List(domain.RunDistributionFilter{ServerInstanceID: job.ServerInstanceID})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, distribution := range runDistributions {
|
|
if distribution.BuildJobID == job.ID {
|
|
expectedArtifactID = distribution.ArtifactID
|
|
break
|
|
}
|
|
}
|
|
if expectedArtifactID == "" {
|
|
clientDistributions, err := svc.store.ClientManagerDistributions().List(domain.ClientManagerDistributionFilter{ServerInstanceID: job.ServerInstanceID})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, distribution := range clientDistributions {
|
|
if distribution.BuildJobID == job.ID {
|
|
expectedArtifactID = distribution.ArtifactID
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if expectedArtifactID == "" {
|
|
return repo.ErrNotFound
|
|
}
|
|
if job.State != domain.JobStateSucceeded {
|
|
return nil
|
|
}
|
|
|
|
artifactID := strings.TrimPrefix(job.ResultRef, "artifact://")
|
|
if artifactID == "" || artifactID == job.ResultRef || artifactID != expectedArtifactID {
|
|
return validationError("distribution build result must reference the expected artifact")
|
|
}
|
|
artifact, err := svc.store.Artifacts().Get(artifactID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if artifact.State != domain.ArtifactStateAvailable || artifact.OwnerKind != domain.ArtifactOwnerKindJob || artifact.OwnerID != job.ID {
|
|
return validationError("distribution build artifact is unavailable or outside the job scope")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func executableFilename(base string, targetOS string) string {
|
|
if targetOS == "windows" {
|
|
return base + ".exe"
|
|
}
|
|
return base
|
|
}
|