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 }