254 lines
11 KiB
Go
254 lines
11 KiB
Go
package engine
|
|
|
|
import "strconv"
|
|
|
|
type acquisitionPolicy struct {
|
|
importDefaults importDefaults
|
|
sequencePolicy sequencePolicy
|
|
importLimits importLimits
|
|
}
|
|
|
|
type acquisitionState struct {
|
|
accounting *importAccounting
|
|
versionState *sourceVersionState
|
|
}
|
|
|
|
type tierAcquisitionState struct {
|
|
visited map[resourceIdentity]importRoute
|
|
hostToken *namespaceToken
|
|
}
|
|
|
|
type inputAcquisitionRequest struct {
|
|
input loadInput
|
|
role sourceVersionRole
|
|
directiveContext rootDirectiveContext
|
|
protectionRule *VersionRule
|
|
deferAssembly bool
|
|
ordinaryOverlay string
|
|
}
|
|
|
|
type importAcquisitionRequest struct {
|
|
input inputAcquisitionRequest
|
|
main parsedSource
|
|
processing *sourceVersionProcessingResult
|
|
}
|
|
|
|
func acquireInput(request inputAcquisitionRequest, policy acquisitionPolicy, state *acquisitionState, tier *tierAcquisitionState) (assembledInput, []Diagnostic) {
|
|
source := request.input.source
|
|
destination := request.input.destination
|
|
accounting := state.accounting
|
|
versionState := state.versionState
|
|
visited := tier.visited
|
|
hostToken := tier.hostToken
|
|
role := request.role
|
|
sourceID := "source-" + strconv.Itoa(accounting.sources+1)
|
|
selectedSource := source
|
|
if source.kind == sourceSearch {
|
|
resolved, failedSource, diagnostics := resolveSearchInput(source, sourceID, defaultDiscoveryDirectoryHelpers(), defaultDiscoveryHostProbe())
|
|
if len(diagnostics) != 0 {
|
|
if failedSource != nil {
|
|
failedSource.Destination = destination.clone()
|
|
failedSource.placementMode = request.input.placementMode
|
|
}
|
|
return assembledInput{source: resolved, sources: sourceInfos(failedSource), versionState: versionState, accounting: accounting}, diagnostics
|
|
}
|
|
selectedSource = resolved
|
|
if request.input.filesystem != nil {
|
|
if diagnostics := filesystemOptionDiagnostics(selectedSource, request.input.filesystem); len(diagnostics) != 0 {
|
|
return assembledInput{source: selectedSource, versionState: versionState, accounting: accounting}, diagnostics
|
|
}
|
|
}
|
|
if selectedSource.kind == sourceNone && activeOverlay(request.ordinaryOverlay) && request.input.filesystem == nil {
|
|
return assembledInput{source: selectedSource, versionState: versionState, accounting: accounting}, []Diagnostic{optionDiagnostic(CodeOptionsInvalid, "NoSource overlays require explicit filesystem context")}
|
|
}
|
|
}
|
|
limits := policy.importLimits
|
|
parseLimits := defaultParseLimits()
|
|
parseLimits.source.remaining = limits.bytes - accounting.bytes
|
|
parsed, diagnostics := parseSource(selectedSource, sourceID, parseLimits)
|
|
if parsed.source != nil {
|
|
parsed.source.Destination = destination.clone()
|
|
parsed.source.placementMode = request.input.placementMode
|
|
}
|
|
input := assembledInput{source: selectedSource, parsed: parsed, document: parsed.document, sources: sourceInfos(parsed.source), versionState: versionState, accounting: accounting}
|
|
if len(diagnostics) != 0 {
|
|
input.sources = sourceInfos(parsed.source)
|
|
// Mapping value issues already identify application content. Other early
|
|
// failures describe source syntax or access, not a placed value.
|
|
if selectedSource.kind == sourceMapping && selectedSource.mapIssue != nil && len(destination.segments) != 0 {
|
|
for index := range diagnostics {
|
|
relative := diagnostics[index].Path
|
|
segments := clonePathSegments(destination.segments, len(relative.segments))
|
|
diagnostics[index].Path = Path{segments: append(segments, relative.segments...)}
|
|
}
|
|
}
|
|
return input, diagnostics
|
|
}
|
|
directiveContext := request.directiveContext
|
|
if directiveContext == 0 {
|
|
directiveContext = rootDirectiveContextImportsOnly
|
|
}
|
|
if !request.input.imports && parsed.document != nil {
|
|
var directiveDiagnostics []Diagnostic
|
|
if directiveContext == rootDirectiveContextSource || directiveContext == rootDirectiveContextLayered {
|
|
directiveDiagnostics = disabledRootImportDiagnostics(parsed.document)
|
|
} else {
|
|
directiveDiagnostics = disabledDirectiveDiagnostics(parsed.document, Path{})
|
|
}
|
|
if len(directiveDiagnostics) != 0 {
|
|
return input, directiveDiagnostics
|
|
}
|
|
}
|
|
var versionProcessing *sourceVersionProcessingResult
|
|
if directiveContext == rootDirectiveContextSource {
|
|
var sourceID string
|
|
if parsed.source != nil {
|
|
sourceID = parsed.source.ID
|
|
}
|
|
var processed sourceVersionProcessingResult
|
|
var failures []Diagnostic
|
|
if versionState != nil {
|
|
processed, failures = versionState.processWithDirectiveContext(parsed.document, role, destination, sourceID, request.input.placementMode, directiveContext)
|
|
} else {
|
|
processed = processSourceVersionWithDirectiveContext(parsed.document, request.protectionRule, role, destination, sourceVersionModeDefault, directiveContext)
|
|
failures = processed.diagnostics
|
|
}
|
|
if len(failures) != 0 {
|
|
return input, failures
|
|
}
|
|
versionProcessing = &processed
|
|
if role == sourceVersionMain && versionState != nil {
|
|
versionState.mainProcessing = &processed
|
|
}
|
|
} else if directiveContext == rootDirectiveContextLayered {
|
|
extracted, failures := extractRootDirectives(parsed.document, rootDirectiveContextLayered)
|
|
if len(failures) != 0 {
|
|
return input, failures
|
|
}
|
|
processed := sourceVersionProcessingResult{
|
|
payload: extracted.body, imports: extracted.imports, sequences: extracted.sequences,
|
|
directiveContext: rootDirectiveContextLayered,
|
|
}
|
|
if versionState != nil && versionState.mode != sourceVersionModeDefault {
|
|
var sourceID string
|
|
if parsed.source != nil {
|
|
sourceID = parsed.source.ID
|
|
}
|
|
versionProcessed, versionFailures := versionState.process(extracted.body, role, destination, sourceID, request.input.placementMode)
|
|
if len(versionFailures) != 0 {
|
|
return input, versionFailures
|
|
}
|
|
versionProcessed.imports = extracted.imports
|
|
versionProcessed.sequences = append(cloneSequenceDeclarations(extracted.sequences), versionProcessed.sequences...)
|
|
versionProcessed.directiveContext = rootDirectiveContextLayered
|
|
processed = versionProcessed
|
|
if role == sourceVersionMain {
|
|
versionState.mainProcessing = &processed
|
|
}
|
|
}
|
|
versionProcessing = &processed
|
|
} else if versionState != nil && versionState.mode != sourceVersionModeDefault {
|
|
var sourceID string
|
|
if parsed.source != nil {
|
|
sourceID = parsed.source.ID
|
|
}
|
|
processed, failures := versionState.process(parsed.document, role, destination, sourceID, request.input.placementMode)
|
|
if len(failures) != 0 {
|
|
return input, failures
|
|
}
|
|
versionProcessing = &processed
|
|
if role == sourceVersionMain {
|
|
versionState.mainProcessing = &processed
|
|
}
|
|
}
|
|
request.input.source = selectedSource
|
|
if request.input.imports && selectedSource.kind != sourceNone {
|
|
importRequest := importAcquisitionRequest{input: request, main: parsed, processing: versionProcessing}
|
|
input.composition, input.sources, input.resolvedFilesystem, diagnostics = acquireImportedInput(importRequest, &policy, state, tier)
|
|
if len(diagnostics) != 0 {
|
|
return input, diagnostics
|
|
}
|
|
if input.composition != nil {
|
|
input.acquisition = cloneAcquisitionScopes(input.composition.acquisition)
|
|
input.document = input.composition.root
|
|
}
|
|
} else {
|
|
input.sources = sourceInfos(parsed.source)
|
|
if versionProcessing != nil {
|
|
input.document = versionProcessing.payload
|
|
}
|
|
if request.input.filesystem != nil && (selectedSource.kind == sourceNone || !activeOverlay(request.ordinaryOverlay)) {
|
|
if diagnostic := validateFilesystemContext(selectedSource, request.input.filesystem, parsed); diagnostic != nil {
|
|
return input, []Diagnostic{*diagnostic}
|
|
}
|
|
}
|
|
}
|
|
if !request.input.imports && input.document != nil {
|
|
_, placed, err := placeDocumentWithMode(input.document, destination, request.input.placementMode, input.document.Location, parseLimits.document.Depth, parseLimits.document.Nodes)
|
|
if err != nil {
|
|
return input, []Diagnostic{sourceDiagnostic(CodeSourceLimitExceeded, parsed.source.ID, "configuration exceeds the source depth or node limit after destination placement")}
|
|
}
|
|
localPayload := input.document
|
|
input.document = placed
|
|
identity := importKeyForMainSource(parsed.source)
|
|
var sequenceDeclarations []sequenceDeclarationSpec
|
|
if versionProcessing != nil {
|
|
sequenceDeclarations = cloneSequenceDeclarations(versionProcessing.sequences)
|
|
}
|
|
input.acquisition = []acquisitionScope{{
|
|
owner: identity,
|
|
destination: destination.clone(),
|
|
contributions: []acquiredContribution{{
|
|
source: cloneSourceInfo(*parsed.source), payload: localPayload,
|
|
identity: identity, role: importRoleMain, scopeOwner: identity,
|
|
document: placed, destination: destination.clone(), processing: versionProcessing,
|
|
sequenceDeclarations: sequenceDeclarations,
|
|
}},
|
|
}}
|
|
rawNodes := countInputNodes(parsed.document)
|
|
placementNodes := destinationWrapperNodeCountForMode(destination, request.input.placementMode)
|
|
if accounting.sources+1 > limits.sources || accounting.bytes+parsed.bytes > limits.bytes || accounting.nodes+rawNodes+placementNodes > limits.nodes {
|
|
return input, []Diagnostic{sourceDiagnostic(CodeSourceLimitExceeded, parsed.source.ID, "configuration exceeds the aggregate input limit")}
|
|
}
|
|
if diagnostic := registerAcquiredSource(selectedSource, request.input.filesystem, parsed.source, visited, hostToken); diagnostic != nil {
|
|
return input, []Diagnostic{*diagnostic}
|
|
}
|
|
accounting.sources++
|
|
accounting.bytes += parsed.bytes
|
|
accounting.nodes += rawNodes + placementNodes
|
|
}
|
|
if !activeOverlay(request.ordinaryOverlay) || request.input.imports {
|
|
if input.resolvedFilesystem == nil && len(input.sources) != 0 && (selectedSource.kind == sourceFile || request.input.filesystem != nil || nativeContext(selectedSource, request.input.filesystem)) {
|
|
resolved, diagnostic := resolveFilesystemContext(selectedSource, request.input.filesystem, parsed, tier.hostToken)
|
|
if diagnostic != nil {
|
|
return input, []Diagnostic{*diagnostic}
|
|
}
|
|
input.resolvedFilesystem = &resolved
|
|
}
|
|
contexts, diagnostic := contextsForResolvedSources(input.resolvedFilesystem, input.sources)
|
|
if diagnostic != nil {
|
|
return input, []Diagnostic{*diagnostic}
|
|
}
|
|
input.contexts = contexts
|
|
}
|
|
return input, nil
|
|
}
|
|
|
|
func registerAcquiredSource(source Source, context *FilesystemContext, info *SourceInfo, visited map[resourceIdentity]importRoute, hostToken *namespaceToken) *Diagnostic {
|
|
if visited == nil || info == nil || info.Path == "" {
|
|
return nil
|
|
}
|
|
identity := resourceIdentity{namespace: hostToken, path: info.Path}
|
|
if source.kind == sourceFilesystem {
|
|
identity.namespace = source.namespace.token
|
|
} else if context != nil && context.FS != nil {
|
|
identity.namespace = context.FS.namespace.token
|
|
}
|
|
if previous, repeated := visited[identity]; repeated {
|
|
value := importRepeatedDiagnostic(Location{SourceID: info.ID}, previous, importRoute{})
|
|
return &value
|
|
}
|
|
visited[identity] = importRoute{canonical: info.Path}
|
|
return nil
|
|
}
|