dataread

package
v0.7.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 1, 2026 License: MPL-2.0 Imports: 24 Imported by: 0

Documentation

Overview

Package dataread is issue #179's pre-resolution data-read phase: provider data sources whose values identity resolution needs, read before resolution runs instead of refused as non-static.

The gap it closes is ordering, not evaluation. Stock OpenTofu reads data sources during the plan walk and has the value before it needs any resource's identity; this fork resolves identity in front of the providers, so a data-source value feeding an identity argument, a count or a for_each used to refuse as non-static even though the provider could have answered. The phase moves the read in front of resolution: the value used is the provider's own answer, never an inference, because any value on this path becomes a live ownership marker and guessing is the one move that is never available.

Two halves, deliberately separable:

  • Analyze is offline: it derives which data sources identity resolution demands (the transitive closure reachable from identity-bearing positions, discovered by probing resolution itself), classifies each as readable-before-the-plan or not, and orders the readable ones over their data-to-data references. live-check runs exactly this and nothing else, keeping its no-cloud-calls contract.
  • Read performs the reads, in Analyze's order, against the same configured provider instances the projection builder already uses (statelessProviders.ConfiguredProvider) - ReadDataSource is the third pre-plan cloud call in the pipeline, reusing the second's plumbing.

Results enter resolution through identity.Context.DataResults; this package never touches the resolver, and internal/live/identity stays cloud-free.

Cost and safety, per #64's prior art: reads are read-only by protocol, O(data source blocks identity needs), and go out through the provider plugin over the same endpoint seam the plan-call budget ratchet counts, so the phase's calls fold into live/plan-budget.json when measured. No caching, ever: a stale hint elsewhere costs a re-read, but a stale data value here becomes a wrong marker - the data-loss shape - so every run reads live, the same price stock OpenTofu pays every plan.

The cross-stack flavors (tfe_outputs, terraform_remote_state) are mechanically provider data sources too, and go through this same eligibility and read pipeline (stages 2 and 3), plus their own auth surface and failure classes: SummaryCrossStackOutputsUnavailable and SummaryCrossStackStateUnavailable. Credential presence itself is never an eligibility question for either flavor - the maintainer's ruling on #181 models the owner (consistent with stage 1's treatment of the aws provider block: eligibility assumes the owner's credentials exist), and their actual absence surfaces honestly at read time instead.

Index

Constants

View Source
const (
	// SummaryCrossStackOutputsUnavailable is tfe_outputs' own failure
	// class, distinct from the generic provider-not-configurable and
	// read-failed classes because the cause is almost always the auth
	// surface rather than the provider block itself: no token argument, no
	// TFE_TOKEN, and no CLI credentials entry for the host (caught offline,
	// as part of eligibility - see [analyzer.tfeAuthAvailable]), or the
	// read itself failing with a workspace-not-found, no-current-state, or
	// permission error (caught at read time, quoted from the provider).
	SummaryCrossStackOutputsUnavailable = "Cross-stack outputs unavailable"

	// SummaryCrossStackStateUnavailable is terraform_remote_state's own
	// failure class (#179 stage 3), the backend analog of
	// [SummaryCrossStackOutputsUnavailable]: the backend it names could not
	// be reached, has no state for the named key or workspace, names a
	// backend type this binary does not link, or holds a state snapshot
	// this fork cannot decode (a newer format, or encryption it cannot
	// open) - always caught at read time, quoted from the backend, never
	// guessed at offline. Eligibility (rule 1) still requires the data
	// source's own backend and config arguments to be statically
	// evaluable; only the backend's actual reachability is deferred to read
	// time, per the same ruling [SummaryCrossStackOutputsUnavailable]
	// documents.
	SummaryCrossStackStateUnavailable = "Cross-stack state unavailable"

	// SummaryNotReadable is eligibility failing: the data source's own
	// arguments, its count/for_each, or something it depends on cannot be
	// evaluated before the plan, so there is nothing honest to read.
	SummaryNotReadable = "Data source not readable before resolution"

	// SummaryProviderNotConfigurable is eligibility rule 3, or configure
	// failing at read time: the data source's provider configuration needs
	// more than static evaluation, or the provider's own configure call
	// refused (bad or missing credentials land here).
	SummaryProviderNotConfigurable = "Data source provider not configurable"

	// SummaryReadFailed is the provider returning an error from the read
	// itself, quoted verbatim. Fatal for the run, the same rule resolution
	// applies to identity holes: a partial identity map plans to create
	// things that exist.
	SummaryReadFailed = "Data source read failed"

	// SummaryProviderNotLive is the root-output read class's own boundary
	// (see [LiveProviders]): the data source is readable by every other rule
	// this phase draws, but its provider manages no live object in this
	// configuration, so this run is not already reading the live system
	// through it. Scoped, never fatal - it costs one root output its prior
	// value and nothing else. It is raised only for an output-demanded
	// source; identity demand does not draw this line, because a source
	// identity needs is one the run must have or refuse.
	SummaryProviderNotLive = "Data source provider manages no live object here"

	// SummaryOutOfScope is GitHub issue #352's targeting boundary: the data
	// source is outside the set of blocks this run's -target or -exclude
	// leaves in the plan graph, so the plan will not read it and neither
	// does this phase. Never fatal for either demand class - see
	// [Source.OutOfScope].
	SummaryOutOfScope = "Data source outside this run's -target scope"

	// SummaryEligibleRead is not a refusal: it is live-check's finding for
	// a site the phase will resolve at plan time with a read. It lives in
	// this registry so the corpus and the generated documentation can name
	// it, and so an estate whose only language findings are these lands on
	// the ladder's data-read-eligible rung rather than language-blocked.
	SummaryEligibleRead = "Resolves at plan time via a data-source read"
)

The phase's summaries, one per class from #179's design. They are constants because every raise site and every consumer (the check layer re-homes identity's data-source refusals under them) must agree on the exact string.

Variables

This section is empty.

Functions

func DataSubject

func DataSubject(subject addrs.Referenceable) (addrs.Resource, bool)

DataSubject extracts the containing data resource from a reference's subject, when it names one. Exported for the check layer, which uses it to map a refusal site back to the data source this analysis classified.

func LiveProviders

func LiveProviders(cfg *configs.Config, declared map[addrs.Provider]map[string]bool) map[addrs.Provider]bool

The phase's provider boundary has TWO tiers, and which one applies is the only thing the two demand classes disagree about. Both are derived per run; neither is a list of provider names.

tier 1, [Boundary.servesLiveObjects] - the provider's own schema declares
        at least one non-logical MANAGED resource type. A provider that
        serves data sources and nothing else is not an infrastructure
        provider, and hashicorp/external - whose whole contract is running
        a program named by its own arguments - is exactly that shape.
        Applies to BOTH classes, and for identity demand it refuses the
        run.

tier 2, [LiveProviders] - the provider manages a live object in THIS
        configuration. Strictly narrower. Applies to the root-output class
        only, where an excluded source costs one output its prior value
        and nothing else, so the stricter line is free.

Why the tiers, rather than one line for both

The root-output class shipped with tier 2 and the identity class shipped with no boundary at all - an adversarial audit on 2026-08-21 found the older, wider-reaching path (#179) completely unconfined, so an ordinary configuration could get data "external" run during a live-plan by putting its result in an identity-bearing position.

The obvious fix, applying tier 2 to both, was measured and rejected: it refuses every configuration whose identity reads a data source of a provider it manages nothing through - data.cloudflare_zone in an aws-managed estate, and this package's own fixtures - none of which can run anything locally, and all of which stock OpenTofu plans without complaint. HANDOFF.md's "parity is the bar" and its corollary that "refusing is not automatically the safe answer" both point the same way: the identity class gets the line that catches the hazard, not the line that was already written.

What tier 1 catches, and what it admits it does not

The property that actually matters is "reading this could run something on the machine planning". Nothing in a provider schema states that, so tier 1 uses the closest thing the schema does state: whether the provider is in the business of managing infrastructure at all. hashicorp/external and hashicorp/http declare no managed types and are excluded; the logical family (hashicorp/random, /null, /time, /tls, /local, the builtin terraform provider) declares only types lint.ClassifyLogicalType measures as logical, whose data sources read the local machine, and is excluded too. Every cloud provider an estate is actually built on is admitted, whether or not this particular configuration manages objects through it.

It does not catch a provider that manages real infrastructure AND ships a data source with a local side effect. No derivation available here would, and stock OpenTofu reads that data source during its own plan, so this is parity rather than a hole this fork opens.

What the tier-2 set means, and why it is not a provider list

The rule tier 2 states is "this run is already reading the live system through this provider, so one more read of the same kind is not a new class of side effect." A pre-plan phase that only ever issues read-only calls to the same remote APIs the projection is already reading keeps live-plan a pure preview of the world.

So the set is derived, per run, from three measurements this repository already keeps, and never from a list of provider names:

  1. The providers this configuration's own MANAGED resources use. A provider with no managed resource type in the configuration is not one this estate owns objects through. hashicorp/external is excluded here for every configuration that can ever be written, because the provider serves no managed resource type at all: it is a data source and nothing else.

  2. Minus the providers whose types are LOGICAL - the store-only and local-effect families internal/live/lint classifies off live/logical-schemas.json (hashicorp/random, /null, /time, /tls, /local and the builtin terraform provider). Their resources have no remote object behind them at all, so a run is not "already reading the live system" through one, and their data sources read the local machine rather than an API.

  3. Intersected with what each provider's OWN SCHEMA declares, when the caller can say (declared, below). (1) reads the provider off configs.Module.ProviderForLocalConfig, which answers with whatever source address a `required_providers` entry bound the resource's local name to - and nothing in that lookup checks that the provider actually serves the type. So

    required_providers { aws = { source = "hashicorp/external" } } resource "aws_s3_bucket" "b" { ... }

    votes hashicorp/external into the live set on the strength of a type it does not serve, and every data source of the local-execution provider becomes readable behind it. Requiring the provider's own schema to declare the type closes that, and it is the provider's measurement rather than ours.

declared is which managed resource types each provider's schema declares, or nil. A provider ABSENT from it is not cross-checked at all: an absent entry means this run never got that provider's schema (the plugin would not start, or the caller had no schema source), which is the absence of evidence rather than evidence of absence, and refusing on it would turn every schema-less caller - live-check, every package-level test - into one that reads nothing. The command layer always supplies it.

All three halves are generated measurements rather than judgments typed here: (1) is read off the configuration, (2) off lint.ClassifyLogicalType, whose table tools/row-gen derives from provider schemas, and (3) off the provider process's own GetProviderSchema. The rule reaches every data source of every cloud provider an estate is built on - for the aws provider alone that is several hundred data source types, not the three that found it - and a future cloud provider this fork supports is covered the day an estate declares a managed resource of it, with no edit here.

func Read

func Read(ctx context.Context, cfg *configs.Config, analysis *Analysis, provs Providers) (map[string]cty.Value, tfdiags.Diagnostics)

Read performs the phase's reads: every eligible demanded source, in dependency order, one ReadDataSource call per data block. The result maps each data resource instance's absolute address to the value the provider returned, shaped for identity.Context.DataResults.

Any demanded source that is not eligible refuses fatally, every one of them at once, before a single network call: a partial value map would make resolution fail with the generic wording on exactly the sites this phase exists to explain. A failed read refuses fatally too, for the rule resolution already applies to identity holes.

That fatality is right for identity and only for identity. The root-output demand class has the opposite contract and its own entry point, ReadForOutputs; handing a scoped analysis to this function is refused rather than honored.

Values are never cached: a stale hint elsewhere costs a re-read, but a stale value here becomes a wrong marker. Every run reads live.

func ReadForOutputs

func ReadForOutputs(ctx context.Context, cfg *configs.Config, analysis *Analysis, provs Providers) (map[string]cty.Value, tfdiags.Diagnostics)

ReadForOutputs performs the reads of a SCOPED analysis - one built by AnalyzeRootOutputs - and is the read half of GitHub issue #349's sub-problem 2.

It differs from Read in what a problem costs, and in nothing else. The same eligibility rules classified these sources, the same provider instances answer them, the same dependency order reads them, and the values come back in the same shape. But a source that is not eligible is SKIPPED rather than refused, and a read that fails is skipped with a warning rather than aborting the run: the only thing either can cost is the prior value of the root output that wanted it, which then renders as newly created in the plan - exactly what it rendered as before this class existed.

An ineligible source raises nothing at all. The plan's own "+ name = ..." line is already the honest report that this output has no prior value, and a per-source diagnostic on every run would say the same thing again, more loudly, about a configuration that is not wrong.

Which of the two contracts applies is read off the analysis (Analysis.Scoped) and not off which of these two functions was called, so a mismatch is not expressible: an analysis is read under the contract it was classified under, whichever entry point a caller reaches for. The two names exist so a call site says which class it is in.

func ReadProviderConfigs

func ReadProviderConfigs(ctx context.Context, cfg *configs.Config, analysis *Analysis, provs Providers) (map[string]cty.Value, tfdiags.Diagnostics)

ReadProviderConfigs performs the reads of a SCOPED analysis built by AnalyzeProviderConfigs. See ReadForOutputs for the shared contract: an ineligible source is skipped in silence and a failed read is skipped with a warning, because the only thing either can cost is the one provider configuration that wanted it, which then fails to configure exactly as it does today.

func ReadableProviders

func ReadableProviders(cfg *configs.Config, analysis *Analysis, declared map[addrs.Provider]map[string]bool) map[addrs.Provider]bool

ReadableProviders flattens a Boundary into the provider set the READ phase may configure for one analysis, which is the shape the command layer's provider seam needs: the seam is handed a provider configuration address and nothing else, so it cannot ask the per-source questions Boundary.Allows asks.

It deliberately does NOT consult Source.Eligible. The seam exists to catch a classification that went wrong or was bypassed - see live_plan.go's liveProviderReads - and a set built from the classification's own verdicts would catch neither. Only the provider and the source's structural class (its cross-stack flavor, which is a property of the type name, not a verdict) decide membership.

Flattening loses the per-source cross-stack exemption: a provider allowed in because ONE of its sources is cross-stack is allowed in for the others too. That widening is bounded and harmless - the classification still refuses those other sources, so the read phase never asks - and it is the price of a seam that owns the provider handle.

func StopsTheRun

func StopsTheRun(summary string) bool

StopsTheRun reports whether a refusal under this Summary can stop a run.

The two demand classes this package serves have opposite contracts, and the registry alone cannot say which one a Summary belongs to. An identity demand that cannot be met refuses the run, because resolution built on a missing value plans to create objects that already exist. A ROOT OUTPUT demand that cannot be met costs exactly one output its prior value; the plan renders it as newly created and everything else proceeds. See ReadForOutputs.

SummaryProviderNotLive answers true because it can now be raised for EITHER class: the boundary it names applies to identity demand as well (see LiveProviders), and there it refuses the run. A Summary is labelled by the worst it can do, not by the commonest.

It exists because tools/limits-gen labels a refusal `error` unless the raising layer says otherwise, and lint and discovery were the only two layers with anything to say. A scoped refusal rendered as a blocker in live/LIMITATIONS.md would be read as something that stops a run, which is the same mistake that once put five discovery warnings in that table as blockers.

Types

type Analysis

type Analysis struct {
	// contains filtered or unexported fields
}

Analysis is Analyze's result: every demanded data source, classified, with the readable ones in an order that reads dependencies first.

func Analyze

func Analyze(ctx context.Context, cfg *configs.Config, opts Options) *Analysis

Analyze derives which data sources identity resolution demands and classifies each as readable-before-the-plan or not. It is offline: no provider process, no cloud call, nothing but the configuration - which is what lets live-check run it under its no-cloud-calls contract.

Demand is discovered by probing resolution itself rather than by reimplementing its notion of an identity-bearing position: resolution is run with placeholder coverage for the data sources found so far, every data-source refusal it still raises names a newly demanded source (the structured configs.RefusedReference carries which), and the loop repeats until resolution demands nothing new. The probe's diagnostics are discarded - the real resolution runs later, with real values.

func AnalyzeProviderConfigs

func AnalyzeProviderConfigs(ctx context.Context, cfg *configs.Config, opts Options) *Analysis

This file is the phase's THIRD demand class, issue #313's boundary: data sources a PROVIDER BLOCK's own arguments reach, rather than data sources an identity or a root output reaches.

The wall it closes: "provider.kubernetes { host = data.aws_eks_cluster. cluster.endpoint }" is refused today not by anything in this package, but by internal/command's statelessProviders.providerConfigValue decoding the block through the module's bare StaticEvaluator - no data lookup, no module-output lookup, nothing this phase already built for every OTHER static-context caller. This class makes the same demand-then-read pipeline outputs.go's own class runs available to that call site: analyze what a provider block's arguments reach, read it, hand the result to [StaticEvaluator.WithDataResults] the same way [liveModuleEvaluator] already does for a data source's own arguments.

It shares every offline eligibility rule (analyze.go's [analyzer.classify]) and the whole read machinery (read.go) with the other two classes, and differs from AnalyzeRootOutputs in only one way, which is why this is a separate entry point mirroring that one rather than a parameter to it:

  • Demand is derived by reading the provider blocks' own argument expressions across every module in the tree - a provider block is not restricted to the root the way a root output is - using the identical four-hop walk [rootOutputDataDemand] already performs (locals, module outputs in either direction, data sources), rooted at a provider block's arguments instead of an output's value.

Fatality is the SAME as AnalyzeRootOutputs, for the same reason: a source this class cannot read is SCOPED, never fatal. A provider whose configuration cannot be resolved this way is not a new failure mode - internal/command's statelessProviders.ConfiguredProvider already reports "Provider unavailable" for it, unchanged, the moment something tries to use it. Making THIS phase fatal over the same gap would only turn one clear diagnostic into two.

func AnalyzeRootOutputs

func AnalyzeRootOutputs(ctx context.Context, cfg *configs.Config, opts Options) *Analysis

AnalyzeRootOutputs derives which data sources the configuration's root-level `output` blocks reach and classifies each one, offline, exactly as Analyze classifies an identity-demanded source, under the same provider Boundary at its stricter tier - see LiveProviders.

The result is an Analysis like any other, so ReadForOutputs can read it with the same machinery, in the same dependency order. Nothing here is fatal and nothing here refuses a configuration: an ineligible source simply carries its reason and is skipped at read time.

GitHub issue #352's -target scope used to be checked here, over the demand roots. It is checked inside [analyzer.classify] instead, because classify recurses and an out-of-scope source demanded only as an in-scope source's DEPENDENCY never passed through a check over the roots - so a -target run still read it.

func (*Analysis) Demanded

func (a *Analysis) Demanded() []*Source

Demanded returns every demanded source, dependencies before dependents.

func (*Analysis) Empty

func (a *Analysis) Empty() bool

Empty reports that identity resolution demands no data sources at all, which is every configuration that worked before this phase existed: the phase then costs nothing.

func (*Analysis) ManagedRefusals

func (a *Analysis) ManagedRefusals() tfdiags.Diagnostics

ManagedRefusals returns every diagnostic this analysis raised over a managed-resource reference [managedProjector] could not answer from a block's own literal arguments - the configs.RefusedReference-carrying diagnostics identity.DemandedManagedReads already knows how to read, regardless of which resolution pass raised them.

This is the seam a caller uses to close issue #187's fixpoint one layer higher than identity's own second pass: analyze once, hand any managed refusals here to identity.DemandedManagedReads alongside a resolution that has already settled the demanded instances' own identities, [projection.ReadInstances] the few instances actually named, supply the result as Options.LiveManagedResults, and analyze again. Nil when nothing here was blocked on a managed resource, which is every configuration this package classified before this method existed.

func (*Analysis) Scoped

func (a *Analysis) Scoped() bool

Scoped reports that this analysis's demand is scoped rather than fatal - see [Analysis.scoped]. Exported so a test can assert which contract an analysis carries without reaching into the struct.

func (*Analysis) SourceFor

func (a *Analysis) SourceFor(module addrs.Module, res addrs.Resource) (*Source, bool)

SourceFor returns the classification for one data resource, when it was demanded.

type Boundary

type Boundary struct {
	// contains filtered or unexported fields
}

Boundary answers, for one run and one demand class, whether the read phase may configure a given provider. It is the whole of the two-tier rule described above, in one object, so the classification half ([analyzer.confineToBoundary]) and the structural half (the command layer's provider seam) cannot draw the line two different ways.

The zero value allows everything; use NewBoundary.

func BoundaryFor

func BoundaryFor(cfg *configs.Config, analysis *Analysis, declared map[addrs.Provider]map[string]bool) Boundary

BoundaryFor builds the boundary for an analysis that already exists, taking the tier from the analysis rather than from the caller. The command layer uses it so that a call site cannot pair a scoped analysis with an unscoped boundary.

func NewBoundary

func NewBoundary(cfg *configs.Config, declared map[addrs.Provider]map[string]bool, scoped bool) Boundary

NewBoundary builds the boundary for one analysis. scoped must be that analysis's own Analysis.Scoped, which is what selects the tier.

func (Boundary) Allows

func (b Boundary) Allows(provider addrs.Provider, crossStack bool) bool

Allows reports whether the read phase may configure this provider.

crossStack exempts the two separately-ruled cross-stack read classes, #179's stages 2 and 3. terraform_remote_state is read through the builtin terraform provider, whose only managed type is logical, so neither tier admits it; tfe_outputs is read through hashicorp/tfe, which no choudoufu estate manages objects through. Both are deliberate, shipped read classes with their own eligibility gates (an auth surface for tfe_outputs, a configurable backend for terraform_remote_state) and their own refusal summaries, both read a remote API, and neither can run a local program - the boundary's actual subject. Excluding them would delete two features to close nothing. The exemption is per SOURCE rather than per provider, so it cannot widen to that provider's other data sources.

type Options

type Options struct {
	// Schemas are the provider's managed resource type schemas, the same
	// map every resolution caller already has ([identity.Context.Schemas]).
	// The analysis probes identity resolution to learn which data sources
	// it demands, and the probe should admit the same types the real
	// resolution will, or a type the schemas admit would hide the demand
	// behind its own refusal.
	Schemas map[string]providers.Schema

	// SkipManagedProjection turns issue #193's managed-argument projection
	// OFF. The projection is ON by default, because it is now complete on
	// both halves: [Analyze] classifies a projectable managed reference and
	// [Read] materializes its value, so a configuration this classifies as
	// readable is one the read phase then reads. While only the
	// classification existed the polarity was the other way round, since a
	// live-check saying "no configuration edit is needed" for a plan that
	// then refuses is worse than the refusal it replaced.
	//
	// This is not a product switch. It exists so a measurement can compute
	// the class's own contribution with and without it, and so this
	// package's tests can assert both sides of the rule from one fixture.
	// See managedproj.go for what is and is not projected.
	SkipManagedProjection bool

	// Scope is which resource blocks a -target / -exclude run leaves in the
	// plan graph (GitHub issue #352), handed straight to the resolution
	// probe below. Demand is read off that probe's own refusals, so a data
	// source demanded only by a resource the run is not acting on is never
	// classified here and never read - which is what stock OpenTofu does
	// with it too, since targeting removes the demander and the data source
	// it pulled in together.
	//
	// Nil is the default and means every block is in scope, which is every
	// untargeted run and every offline caller. See [identity.Scope].
	Scope identity.Scope

	// ProviderManagedTypes is which managed resource types each provider's
	// own schema declares, keyed by provider - the cross-check half of
	// [LiveProviders]' derivation, and the reason a `required_providers`
	// entry cannot vote a provider into the live set on the strength of a
	// type that provider does not serve.
	//
	// Nil, or a provider absent from it, means "this run has no schema for
	// that provider" and skips the cross-check for it rather than refusing
	// on it. See [LiveProviders].
	ProviderManagedTypes map[addrs.Provider]map[string]bool

	// LiveManagedResults is a real, narrow live read of specific managed
	// resource instances - [projection.ReadInstances]'s own output shape,
	// keyed by absolute instance address - that a caller performed AHEAD of
	// this call, after a first, plain analysis named which instances it
	// needed. It answers a managed-resource reference [managedProj] cannot
	// project from the block's own literal arguments (an attribute the body
	// does not set, such as an "id" that only the provider assigns), for
	// the one instance a caller actually read and no other. See
	// [managedProjector.liveManaged].
	//
	// Nil is the default, and every existing caller gets it: a managed
	// reference this cannot answer keeps refusing exactly as it always has,
	// carrying the same [configs.RefusedReference] a caller can read to
	// learn what a live read would need to name (see
	// [identity.DemandedManagedReads], which reads any [tfdiags.Diagnostics]
	// this package's own evaluation raises, not only identity's own).
	LiveManagedResults map[string]cty.Value
}

Options is what a caller may tell the analysis about the world outside the configuration. Everything is optional; the zero value analyzes with the configuration alone.

type Providers

type Providers interface {
	ConfiguredProvider(ctx context.Context, addr addrs.AbsProviderConfig) (providers.Interface, error)
}

Providers is the one seam the read phase needs from its caller: a configured provider instance per provider configuration. The command layer's statelessProviders satisfies it - the same instances the projection builder calls ImportResourceState and ReadResource on, so the phase adds a verb, not a plumbing.

type Refusal

type Refusal struct {
	// Summary is the hcl.Diagnostic Summary, and this refusal's identity.
	Summary string

	// What is a one-line description of the situation that triggers it.
	What string

	// Doc overrides where it is documented. Empty means the generated
	// entry under its own Summary; see identity.Refusal.Doc.
	Doc string
}

Refusal is one thing this package can refuse, keyed by the Summary its diagnostic carries.

func LookupRefusal

func LookupRefusal(summary string) (Refusal, bool)

LookupRefusal returns the registry entry for one Summary.

func Refusals

func Refusals() []Refusal

Refusals returns every refusal this package can produce, sorted by Summary.

func (Refusal) DocsRef

func (r Refusal) DocsRef() string

DocsRef is where a user is sent to read about this refusal.

type Source

type Source struct {
	// Module is the module path declaring the block, unkeyed.
	Module addrs.Module

	// Resource is the module-relative data resource address.
	Resource addrs.Resource

	// Config is the declaring block.
	Config *configs.Resource

	// NeededBy names what demanded this source: the identity-bearing
	// identifier whose evaluation referenced it, or the demanding data
	// source for a transitive dependency.
	NeededBy string

	// TfeOutputs marks a tfe_outputs source. It goes through the same
	// eligibility pipeline as a same-stack source. Credential presence
	// itself is not an eligibility question - the maintainer's ruling on
	// #181 (eligibility models the owner, consistent with stage 1's
	// treatment of the aws provider block): a token argument, TFE_TOKEN, or
	// a CLI credentials entry is assumed to exist for the owner running
	// this, and its actual absence surfaces as a read-time refusal under
	// [SummaryCrossStackOutputsUnavailable] instead (see [reader.readSource]
	// in read.go).
	TfeOutputs bool

	// RemoteState marks a terraform_remote_state source. #179 stage 3 gives
	// it the same eligibility pipeline a same-stack source gets: its own
	// arguments (the backend type, its config object, the workspace) must
	// be statically evaluable, the same rules 1/2/4 any data source draws.
	// Backend credentials are assumed present for eligibility, the same
	// ruling [Source.TfeOutputs] documents; an absent, unreachable, or
	// undecodable backend refuses honestly at read time under
	// [SummaryCrossStackStateUnavailable].
	RemoteState bool

	// Eligible reports that the phase can read this source before the plan:
	// static arguments and count/for_each, a statically configurable
	// provider, and no managed-resource dependency.
	Eligible bool

	// OutOfScope marks a source this run's -target / -exclude leaves out of
	// the plan graph (GitHub issue #352). It is ineligible, and it is the one
	// kind of ineligible source that never stops a run even under [Read]'s
	// otherwise-fatal contract: a block the plan will not act on is not a
	// hole in the identity map, it is a block outside this run entirely, and
	// refusing over one would turn every -target run into a refusal of the
	// configuration's untargeted half. Reading it anyway is the thing that
	// would be wrong - it puts a prior value in front of a diff whose other
	// side the plan never computes.
	OutOfScope bool

	// PerInstance reports that at least one of this block's own arguments -
	// directly, or inside a nested block - reads this same block's own
	// count.index or each.key/each.value. That is ordinary per-block
	// repetition scoping, the same scoping every resource and data block
	// gets in stock OpenTofu (internal/tofu/evaluate.go's
	// evaluationStateData.GetCountAttr/GetForEachAttr, bound per instance
	// from the block's own expansion), not a dynamic value: each instance's
	// key or value is known the moment the block's own count/for_each is
	// known, the same round this analysis already evaluates it in.
	//
	// It means instances of this block can have genuinely different
	// arguments, so [Read] cannot share one provider answer across every
	// instance the way it does for a block whose arguments are
	// instance-invariant; it reads each instance separately instead, with
	// that instance's own count.index or each.key/each.value bound - one
	// provider call per instance, the same cost stock OpenTofu's own
	// per-instance data-source read already pays
	// (internal/tofu/node_resource_abstract_instance.go, readDataSource).
	PerInstance bool

	// ReasonSummary and ReasonDetail are the refusal for an ineligible
	// source: ReasonSummary is one of this package's Summary constants and
	// ReasonDetail the class-specific sentence.
	ReasonSummary string
	ReasonDetail  string

	// Deps are the data sources this one references, directly, through
	// locals, in depends_on, or - since #212 - through a module-call
	// variable that reaches back into an ancestor module's own data source:
	// Dep.Module names the DECLARING module, which is this Source's own
	// Module for an ordinary same-module reference and an ancestor's for a
	// cross-module one. These are the edges the topological read order
	// follows. Sorted.
	Deps []SourceDep
}

Source is one data resource block the analysis classified: demanded by an identity-bearing position (directly, or transitively through another data source), and either readable before the plan or refused with a reason.

type SourceDep

type SourceDep struct {
	Module   addrs.Module
	Resource addrs.Resource
}

SourceDep is one dependency edge: the module and resource of a data source Source.Deps's owner references. Module can differ from the referencing Source's own Module - see Source.Deps's doc.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL