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 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 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 } } request.directiveContext = directiveContext versionProcessing, diagnostics := processAcquiredInputVersion(parsed, request, versionState) if len(diagnostics) != 0 { return input, diagnostics } 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, diagnostics = acquireStandaloneInput(input, request, versionProcessing, policy, tier) if len(diagnostics) != 0 { return input, diagnostics } } return completeAcquisitionContexts(input, request, tier) } func acquireStandaloneInput(input assembledInput, request inputAcquisitionRequest, processing *sourceVersionProcessingResult, policy acquisitionPolicy, tier *tierAcquisitionState) (assembledInput, []Diagnostic) { parsed := input.parsed input.sources = sourceInfos(parsed.source) if processing != nil { input.document = processing.payload } if request.input.filesystem != nil && (input.source.kind == sourceNone || !activeOverlay(request.ordinaryOverlay)) { resolved, diagnostic := validateFilesystemContext(input.source, request.input.filesystem, parsed, tier.hostToken) if diagnostic != nil { return input, []Diagnostic{*diagnostic} } input.resolvedFilesystem = &resolved } if request.input.imports || input.document == nil { return input, nil } limits := policy.importLimits placement := request.input.destination documentLimits := defaultParseLimits().document _, placed, err := placeDocumentWithMode(input.document, placement, request.input.placementMode, input.document.Location, documentLimits.Depth, documentLimits.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 processing != nil { sequenceDeclarations = cloneSequenceDeclarations(processing.sequences) } input.acquisition = []acquisitionScope{{ owner: identity, destination: placement.clone(), contributions: []acquiredContribution{{ source: cloneSourceInfo(*parsed.source), payload: localPayload, identity: identity, role: importRoleMain, scopeOwner: identity, document: placed, destination: placement.clone(), processing: processing, sequenceDeclarations: sequenceDeclarations, }}, }} rawNodes := countInputNodes(parsed.document) placementNodes := destinationWrapperNodeCountForMode(placement, request.input.placementMode) accounting := input.accounting 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(input.source, request.input.filesystem, parsed.source, tier.visited, tier.hostToken); diagnostic != nil { return input, []Diagnostic{*diagnostic} } accounting.sources++ accounting.bytes += parsed.bytes accounting.nodes += rawNodes + placementNodes return input, nil } func completeAcquisitionContexts(input assembledInput, request inputAcquisitionRequest, tier *tierAcquisitionState) (assembledInput, []Diagnostic) { if activeOverlay(request.ordinaryOverlay) && !request.input.imports { return input, nil } if input.resolvedFilesystem == nil && len(input.sources) != 0 && (input.source.kind == sourceFile || request.input.filesystem != nil || nativeContext(input.source, request.input.filesystem)) { resolved, diagnostic := resolveFilesystemContext(input.source, request.input.filesystem, input.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 processAcquiredInputVersion(parsed parsedSource, request inputAcquisitionRequest, state *sourceVersionState) (*sourceVersionProcessingResult, []Diagnostic) { sourceID := "" if parsed.source != nil { sourceID = parsed.source.ID } var result sourceVersionProcessingResult hasResult := false updateMainProcessing := false switch request.directiveContext { case rootDirectiveContextSource: if state != nil { processed, diagnostics := state.processWithDirectiveContext( parsed.document, request.role, request.input.destination, sourceID, request.input.placementMode, request.directiveContext, ) if len(diagnostics) != 0 { return nil, diagnostics } result = processed updateMainProcessing = request.role == sourceVersionMain } else { result = processSourceVersionWithDirectiveContext( parsed.document, request.protectionRule, request.role, request.input.destination, sourceVersionModeDefault, request.directiveContext, ) if len(result.diagnostics) != 0 { return nil, result.diagnostics } } hasResult = true case rootDirectiveContextLayered: extracted, diagnostics := extractRootDirectives(parsed.document, request.directiveContext) if len(diagnostics) != 0 { return nil, diagnostics } result = sourceVersionProcessingResult{ payload: extracted.body, imports: extracted.imports, sequences: extracted.sequences, directiveContext: request.directiveContext, } hasResult = true if state != nil && state.mode != sourceVersionModeDefault { processed, diagnostics := state.process( extracted.body, request.role, request.input.destination, sourceID, request.input.placementMode, ) if len(diagnostics) != 0 { return nil, diagnostics } processed.imports = extracted.imports processed.sequences = append(cloneSequenceDeclarations(extracted.sequences), processed.sequences...) processed.directiveContext = request.directiveContext result = processed updateMainProcessing = request.role == sourceVersionMain } default: if state != nil && state.mode != sourceVersionModeDefault { processed, diagnostics := state.process( parsed.document, request.role, request.input.destination, sourceID, request.input.placementMode, ) if len(diagnostics) != 0 { return nil, diagnostics } result = processed hasResult = true updateMainProcessing = request.role == sourceVersionMain } } if !hasResult { return nil, nil } if updateMainProcessing { completed := result state.mainProcessing = &completed } return &result, 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 }