Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
5981cd2
Prepare main branch for post-v1.14 development (#37621)
SarahFrench Sep 16, 2025
719aefd
tf query: ready list blocks for beta (#37619)
dsa0x Sep 17, 2025
15a6cd2
stacks: fix diagnostic referring to tfstacks command (#37639)
liamcervante Sep 19, 2025
0dfa115
First step to enable use of TF_REATTACH_PROVIDERS with PSS (#37634)
SarahFrench Sep 19, 2025
e241e1a
actions: return an error if config is omitted but the schema has requ…
mildwonkey Sep 19, 2025
10f00d4
Use raw args in -help flag handling
Maed223 Sep 19, 2025
7f0ef4c
changelog entry
Maed223 Sep 19, 2025
e65c562
actions: allow invoke only on local execution while TFC adds support …
liamcervante Sep 22, 2025
f8b0051
Merge pull request #37645 from hashicorp/handle-help-usage-stacks-plugin
Maed223 Sep 22, 2025
8097ce9
docs(destroying): fix typos
Tensho Sep 18, 2025
152ce1d
Merge pull request #37638 from Tensho/fix-destroying-docs
crw Sep 23, 2025
f6ad8d3
actions: fix panic during query plan by not planning actions
mildwonkey Sep 23, 2025
a750471
list: send non-null "config" object to provider when not present in t…
dsa0x Sep 24, 2025
1e41449
evaluate: return diagnostics instead of unknown for uninitialised loc…
liamcervante Sep 24, 2025
b952256
PSS: Add reusable method for obtaining the provider factory needed fo…
SarahFrench Sep 24, 2025
ab41592
query: propagate graph node removal to descendants (#37664)
dsa0x Sep 25, 2025
a4cc769
actions: add extra test cases around conditions referencing the trigg…
DanielMSchmidt Sep 25, 2025
51fc4c1
PSS: Implement `ReadStateBytes` + `WriteStateBytes` (#37440)
radeksimko Sep 26, 2025
8ed2f39
actions: don't panic if nil changes - it might be a refresh (#37687)
mildwonkey Sep 26, 2025
c0547e7
Fixes to protobuf compile script (#37683)
SarahFrench Sep 26, 2025
eae5ac1
build: Produce a Windows arm64 binary (#35461)
xiehan Sep 26, 2025
0217dd2
Revert "temporarily require manager approval for go.mod changes" (#37…
dsa0x Sep 29, 2025
ffbb62b
Swap to using `google.golang.org/protobuf/cmd/protoc-gen-go` instead …
SarahFrench Sep 30, 2025
8986651
actions: disable support for deferrals (#37700)
liamcervante Oct 1, 2025
2274026
query: add -query flag to validate command (#37671)
dsa0x Oct 1, 2025
d802fe3
Modernize Go
dbanck Oct 1, 2025
5c0b37a
backport of commit d802fe39cc31bbbdb306c24e6aa0db81a53ee16d
dbanck Oct 1, 2025
b2b8f62
backport of commit a175b1750b3c3118721c4f2ad11f87c3723c3f08
dbanck Oct 1, 2025
693ca6c
Merge d802fe39cc31bbbdb306c24e6aa0db81a53ee16d into backport/dbanck/c…
github-actions[bot] Oct 6, 2025
f606451
backport of commit 87ee7e393890987081fd4f8f1b5e30caa5afd355
dbanck Oct 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/v1.14/BUG FIXES-20250924-110416.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: BUG FIXES
body: 'console and test: return explicit diagnostics when referencing resources that were not included in the most recent operation.'
time: 2025-09-24T11:04:16.860364+02:00
custom:
Issue: "37663"
5 changes: 5 additions & 0 deletions .changes/v1.14/BUG FIXES-20250926-113318.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: BUG FIXES
body: 'query: generate unique resource identifiers for results of expanded list resources'
time: 2025-09-26T11:33:18.241184+02:00
custom:
Issue: "37681"
5 changes: 5 additions & 0 deletions .changes/v1.14/BUGFIX-20250927-184134.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: BUGFIX
body: The CLI now summarizes the number of actions invoked during `terraform apply`, matching the plan output.
time: 2025-09-27T18:41:34.771437+02:00
custom:
Issue: "37689"
5 changes: 5 additions & 0 deletions .changes/v1.14/ENHANCEMENTS-20250919-115253.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: ENHANCEMENTS
body: '`terraform stacks` command support for `-help` flag'
time: 2025-09-19T11:52:53.923764-04:00
custom:
Issue: "37645"
5 changes: 5 additions & 0 deletions .changes/v1.14/ENHANCEMENTS-20250925-151237.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: ENHANCEMENTS
body: "query: support offline validation of query files via -query flag in the validate command"
time: 2025-09-25T15:12:37.198573+02:00
custom:
Issue: "37671"
3 changes: 3 additions & 0 deletions .changes/v1.14/NEW FEATURES-20250829-183404.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
kind: NEW FEATURES
body: "**List Resources**: List resources can be defined in `*.tfquery.hcl` files and allow querying and filterting existing infrastructure."
time: 2025-08-29T18:34:04.250038+02:00
3 changes: 3 additions & 0 deletions .changes/v1.14/NEW FEATURES-20250829-184206.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
kind: NEW FEATURES
body: "A new Terraform command `terraform query`: Executes list operations against existing infrastructure and displays the results. The command can optionally generate configuration for importing results into Terraform."
time: 2025-08-29T18:42:06.659172+02:00
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,32 @@

NEW FEATURES:

* **List Resources**: List resources can be defined in `*.tfquery.hcl` files and allow querying and filterting existing infrastructure.

* A new Terraform command `terraform query`: Executes list operations against existing infrastructure and displays the results. The command can optionally generate configuration for importing results into Terraform.

* A new GenerateResourceConfiguration RPC allows providers to create more precise configuration values during import. ([#37515](https://github.com/hashicorp/terraform/issues/37515))

* New top-level Actions block: Actions are provider defined and meant to codify use cases outside the normal CRUD model in your Terraform configuration. Providers can define Actions like `aws_lambda_invoke` or `aws_cloudfront_create_invalidation` that do something imparative outside of Terraforms normal CRUD model. You can configure such a side-effect with an action block and have actions triggered through the lifecycle of a resource or through passing the `-invoke` CLI flag. ([#37553](https://github.com/hashicorp/terraform/issues/37553))


ENHANCEMENTS:

* terraform test: expected diagnostics will be included in test output when running in verbose mode" ([#37362](https://github.com/hashicorp/terraform/issues/37362))

* terraform test: ignore prevent_destroy attribute during when cleaning up tests" ([#37364](https://github.com/hashicorp/terraform/issues/37364))

* `terraform stacks` command support for `-help` flag ([#37645](https://github.com/hashicorp/terraform/issues/37645))


BUG FIXES:

* Retrieve all workspace variables while doing a `terraform import`, include variables inherited from variable sets but not overwritten by the workspace. ([#37241](https://github.com/hashicorp/terraform/issues/37241))

* Fix OSS backend proxy support by adding a proxy layer for OSS backend operations. Resolves hashicorp/terraform#36897. ([#36897](https://github.com/hashicorp/terraform/issues/36897))

* console and test: return explicit diagnostics when referencing resources that were not included in the most recent operation. ([#37663](https://github.com/hashicorp/terraform/issues/37663))


UPGRADE NOTES:

Expand Down
3 changes: 0 additions & 3 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,3 @@
builtin/provisioners/file @hashicorp/terraform-core
builtin/provisioners/local-exec @hashicorp/terraform-core
builtin/provisioners/remote-exec @hashicorp/terraform-core

# temporary go.mod protection during freeze
/go.mod @tommyokeefe
12 changes: 6 additions & 6 deletions commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,12 @@ func initCommands(
}, nil
},

"query": func() (cli.Command, error) {
return &command.QueryCommand{
Meta: meta,
}, nil
},

"refresh": func() (cli.Command, error) {
return &command.RefreshCommand{
Meta: meta,
Expand Down Expand Up @@ -451,12 +457,6 @@ func initCommands(
}, nil
}

Commands["query"] = func() (cli.Command, error) {
return &command.QueryCommand{
Meta: meta,
}, nil
}

Commands["test cleanup"] = func() (cli.Command, error) {
return &command.TestCleanupCommand{
Meta: meta,
Expand Down
4 changes: 3 additions & 1 deletion internal/backend/local/backend_local.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,9 @@ func (b *Local) localRun(op *backendrun.Operation) (*backendrun.LocalRun, *confi
// If validation is enabled, validate
if b.OpValidation {
log.Printf("[TRACE] backend/local: running validation operation")
validateDiags := ret.Core.Validate(ret.Config, nil)
// TODO: Implement query validate command. op.Query is false when running the command "terraform validate"
opts := &terraform.ValidateOpts{Query: op.Query}
validateDiags := ret.Core.Validate(ret.Config, opts)
diags = diags.Append(validateDiags)
}
}
Expand Down
7 changes: 4 additions & 3 deletions internal/cloud/backend_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/hashicorp/terraform/internal/genconfig"
"github.com/hashicorp/terraform/internal/terraform"
"github.com/hashicorp/terraform/internal/tfdiags"
"github.com/zclconf/go-cty/cty"
ctyjson "github.com/zclconf/go-cty/cty/json"
)

Expand Down Expand Up @@ -276,7 +277,7 @@ func (b *Cloud) cancelQueryRun(cancelCtx context.Context, op *backendrun.Operati
// formatIdentity formats the identity map into a string representation.
// It flattens the map into a string of key=value pairs, separated by commas.
func formatIdentity(identity map[string]json.RawMessage) string {
parts := make([]string, 0, len(identity))
ctyObj := make(map[string]cty.Value, len(identity))
for key, value := range identity {
ty, err := ctyjson.ImpliedType(value)
if err != nil {
Expand All @@ -286,9 +287,9 @@ func formatIdentity(identity map[string]json.RawMessage) string {
if err != nil {
continue
}
parts = append(parts, fmt.Sprintf("%s=%s", key, tfdiags.ValueToString(v)))
ctyObj[key] = v
}
return strings.Join(parts, ",")
return tfdiags.ObjectToString(cty.ObjectVal(ctyObj))
}

const queryDefaultHeader = `
Expand Down
12 changes: 10 additions & 2 deletions internal/cloud/backend_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/cli"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/backend/backendrun"
Expand Down Expand Up @@ -121,8 +122,15 @@ func TestCloud_queryJSONBasic(t *testing.T) {
outp := close(t)
gotOut := outp.Stdout()

if !strings.Contains(gotOut, "list.concept_pet.pets id=complete-gannet,legs=6 This is a complete-gannet") {
t.Fatalf("expected query results in output: %s", gotOut)
expectedOut := `list.concept_pet.pets id=large-roughy,legs=2 This is a large-roughy
list.concept_pet.pets id=able-werewolf,legs=5 This is a able-werewolf
list.concept_pet.pets id=complete-gannet,legs=6 This is a complete-gannet
list.concept_pet.pets id=charming-beagle,legs=3 This is a charming-beagle
list.concept_pet.pets id=legal-lamprey,legs=2 This is a legal-lamprey

`
if diff := cmp.Diff(expectedOut, gotOut); diff != "" {
t.Fatalf("expected query results output to be %s, got %s: diff: %s", expectedOut, gotOut, diff)
}

stateMgr, _ := b.StateMgr(testBackendSingleWorkspaceName)
Expand Down
4 changes: 3 additions & 1 deletion internal/cloud/testdata/query-json-basic/main.tfquery.hcl
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
list "concept_pet" "pets" {}
list "concept_pet" "pets" {
provider = concept
}
4 changes: 3 additions & 1 deletion internal/cloud/testdata/query/main.tfquery.hcl
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
list "null_resource" "foo" {}
list "null_resource" "foo" {
provider = null
}
4 changes: 4 additions & 0 deletions internal/command/arguments/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ type Validate struct {

// ViewType specifies which output format to use: human, JSON, or "raw".
ViewType ViewType

// Query indicates that Terraform should also validate .tfquery files.
Query bool
}

// ParseValidate processes CLI arguments, returning a Validate value and errors.
Expand All @@ -40,6 +43,7 @@ func ParseValidate(args []string) (*Validate, tfdiags.Diagnostics) {
cmdFlags.BoolVar(&jsonOutput, "json", false, "json")
cmdFlags.StringVar(&validate.TestDirectory, "test-directory", "tests", "test-directory")
cmdFlags.BoolVar(&validate.NoTests, "no-tests", false, "no-tests")
cmdFlags.BoolVar(&validate.Query, "query", false, "query")

if err := cmdFlags.Parse(args); err != nil {
diags = diags.Append(tfdiags.Sourceless(
Expand Down
30 changes: 30 additions & 0 deletions internal/command/e2etest/providers_schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,21 @@ func TestProvidersSchema(t *testing.T) {
}
}
},
"list_resource_schemas": {
"simple_resource": {
"version": 0,
"block": {
"attributes": {
"value": {
"type": "string",
"description_kind": "plain",
"optional": true
}
},
"description_kind": "plain"
}
}
},
"resource_identity_schemas": {
"simple_resource": {
"version": 0,
Expand Down Expand Up @@ -213,6 +228,21 @@ func TestProvidersSchema(t *testing.T) {
}
}
},
"list_resource_schemas": {
"simple_resource": {
"version": 0,
"block": {
"attributes": {
"value": {
"type": "string",
"description_kind": "plain",
"optional": true
}
},
"description_kind": "plain"
}
}
},
"functions": {
"noop": {
"description": "noop takes any single argument and returns the same value",
Expand Down
6 changes: 3 additions & 3 deletions internal/command/jsonformat/plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8071,7 +8071,7 @@ func runTestCases(t *testing.T, testCases map[string]testCase) {
return
}

jsonschemas := jsonprovider.MarshalForRenderer(tfschemas, false)
jsonschemas := jsonprovider.MarshalForRenderer(tfschemas)
change := structured.FromJsonChange(jsonchanges[0].Change, attribute_path.AlwaysMatcher())
renderer := Renderer{Colorize: color}
diff := diff{
Expand Down Expand Up @@ -8421,7 +8421,7 @@ func TestResourceChange_deferredActions(t *testing.T) {
}

renderer := Renderer{Colorize: color}
jsonschemas := jsonprovider.MarshalForRenderer(fullSchema, false)
jsonschemas := jsonprovider.MarshalForRenderer(fullSchema)
diffs := precomputeDiffs(Plan{
DeferredChanges: deferredChanges,
ProviderSchemas: jsonschemas,
Expand Down Expand Up @@ -8714,7 +8714,7 @@ func TestResourceChange_actions(t *testing.T) {
},
},
}
jsonschemas := jsonprovider.MarshalForRenderer(fullSchema, false)
jsonschemas := jsonprovider.MarshalForRenderer(fullSchema)
diffs := precomputeDiffs(Plan{
ResourceChanges: []jsonplan.ResourceChange{defaultResourceChange},
ActionInvocations: tc.actionInvocations,
Expand Down
2 changes: 1 addition & 1 deletion internal/command/jsonformat/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func TestState(t *testing.T) {
RootModule: root,
RootModuleOutputs: outputs,
ProviderFormatVersion: jsonprovider.FormatVersion,
ProviderSchemas: jsonprovider.MarshalForRenderer(tt.Schemas, false),
ProviderSchemas: jsonprovider.MarshalForRenderer(tt.Schemas),
})

result := done(t).All()
Expand Down
32 changes: 15 additions & 17 deletions internal/command/jsonprovider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,22 +45,22 @@ func newProviders() *Providers {
// schema into the public structured JSON versions.
//
// This is a format that can be read by the structured plan renderer.
func MarshalForRenderer(s *terraform.Schemas, includeExperimentalSchemas bool) map[string]*Provider {
func MarshalForRenderer(s *terraform.Schemas) map[string]*Provider {
schemas := make(map[string]*Provider, len(s.Providers))
for k, v := range s.Providers {
schemas[k.String()] = marshalProvider(v, includeExperimentalSchemas)
schemas[k.String()] = marshalProvider(v)
}
return schemas
}

func Marshal(s *terraform.Schemas, includeExperimentalSchemas bool) ([]byte, error) {
func Marshal(s *terraform.Schemas) ([]byte, error) {
providers := newProviders()
providers.Schemas = MarshalForRenderer(s, includeExperimentalSchemas)
providers.Schemas = MarshalForRenderer(s)
ret, err := json.Marshal(providers)
return ret, err
}

func marshalProvider(tps providers.ProviderSchema, includeExperimentalSchemas bool) *Provider {
func marshalProvider(tps providers.ProviderSchema) *Provider {
p := &Provider{
Provider: marshalSchema(tps.Provider),
ResourceSchemas: marshalSchemas(tps.ResourceTypes),
Expand All @@ -71,20 +71,18 @@ func marshalProvider(tps providers.ProviderSchema, includeExperimentalSchemas bo
ActionSchemas: marshalActionSchemas(tps.Actions),
}

if includeExperimentalSchemas {
// List resource schemas are nested under a "config" block, so we need to
// extract that block to get the actual provider schema for the list resource.
// When getting the provider schemas, Terraform adds this extra level to
// better match the actual configuration structure.
listSchemas := make(map[string]providers.Schema, len(tps.ListResourceTypes))
for k, v := range tps.ListResourceTypes {
listSchemas[k] = providers.Schema{
Body: &v.Body.BlockTypes["config"].Block,
Version: v.Version,
}
// List resource schemas are nested under a "config" block, so we need to
// extract that block to get the actual provider schema for the list resource.
// When getting the provider schemas, Terraform adds this extra level to
// better match the actual configuration structure.
listSchemas := make(map[string]providers.Schema, len(tps.ListResourceTypes))
for k, v := range tps.ListResourceTypes {
listSchemas[k] = providers.Schema{
Body: &v.Body.BlockTypes["config"].Block,
Version: v.Version,
}
p.ListResourceSchemas = marshalSchemas(listSchemas)
}
p.ListResourceSchemas = marshalSchemas(listSchemas)

return p
}
Loading