Commit 94510bc1 authored by Martin Atkins's avatar Martin Atkins
Browse files

states/statemgr: Migrate, Import, and Export functions

In our recent refactoring of the state manager interfaces we made serial
and lineage management the responsibility of the state managers
themselves, not exposing them at all to most callers, and allowing for
simple state managers that don't implement them at all.

However, we do have some specific cases where we need to preserve these
properly when available, such as migration between backends, and the
"terraform state push" and "terraform state pull" commands.

These new functions and their associated optional interface allow the
logic here to be captured in one place and access via some simple
calls. Separating this from the main interface leaves things simple for
the normal uses of state managers.

Since these functions are mostly just thin wrappers around other
functionality, they are not yet well-tested directly, but will be
indirectly tested through the tests of their callers. A subsequent commit
will add more unit tests here.
parent 22933912
main 0.15.3 26258 Incident16886 MatthewTestBranch add-cont-valid-callout add-internals-to-sidebar add-jsonstate-to-cloudbackendstate add-jsonstate-to-cloudbackendstate2 add-learn-callout-moved-blocks add-new-intro-docs add-note-about-spaces add-page-metadata add-tutorial-custom-conditions add-version-notes-1.2 add-warnings-backends alisdair/alpine-latest alisdair/are-consul-tests-running alisdair/backend-config-override-fix-remote-test alisdair/concise-diff-experiment alisdair/disable-preconditions-postconditions alisdair/duplicate-no-color alisdair/fix-configload-snapshot-panic alisdair/fix-remote-backend-migrate-version-check alisdair/getproviders-retries-bad-branch-do-not-use alisdair/metadata-functions-command alisdair/mildwonkey/type-func alisdair/plan-summary-after-changes alisdair/prototype-moved-actions alisdair/providers-list alisdair/remote-input-variable-unset alisdair/resource-instance-object-dependencies alisdair/still-applying-consolidation alisdair/version-should-include-prerelease b-1.1-module-source-git b-check-output-multi-expand b-check-resource-multi-expand b-dev-overrides-plan-warning b-diags-sensitive-value b-flatten-panic b-miekg-dns-upgrade b-module-instance-resource-deps b-move-implied-cross-package b-nested-set-unknown b-sdk-import-verify-test b-sdk-nested-id b-tf-builtin-provider-parser-fix b-type-conversion-funcs-null b-yamldecode-emptydoc-null backport/29156_do_not_log_sensitive_values/nearly-safe-bullfrog backport/4th-alternate-mirror-directory-fix/closely-key-civet backport/add-cont-valid-callout/luckily-golden-wildcat backport/add-format-function-guidance/ultimately-lucky-halibut backport/add-internals-to-sidebar/willingly-bright-sunbird backport/add-learn-callout-moved-blocks/multiply-awaited-cub backport/add-new-intro-docs/strangely-lucky-bluegill backport/add-powershell-warning/certainly-amazing-lemur backport/add-version-notes-1.2/amazingly-premium-woodcock backport/add-warnings-backends/physically-many-mantis backport/alisdair/cty-1.8.3/seriously-tidy-foal backport/alisdair/fix-30641/firmly-musical-lemming backport/alisdair/fix-show-plan-against-non-default-state/blatantly-balanced-porpoise backport/alisdair/fix-stuck-lock-when-applying-state-plan/enormously-refined-sheepdog backport/alisdair/json-ui-resource-drift/broadly-careful-rooster backport/alisdair/pre-convert-optional-defaults/virtually-able-mouse backport/alisdair/redact-sensitive-values-from-function-errors/definitely-assured-mantis backport/alisdair/sensitive-attribute-forces-replacement/mildly-optimal-platypus backport/b-check-resource-multi-expand/extremely-key-sheep backport/backport/cstella84/patch-add-hyperlink-for-referenced-argument/manually-patient-locust backport/barrettclark/update-go-slug/severely-destined-crow backport/bugfix_typos/hardly-sharp-mosquito backport/clarify-backend-state-storage/ultimately-causal-ladybird backport/consul-size-limit/amazingly-innocent-python backport/cstella84/patch-add-hyperlink-for-referenced-argument backport/description-metadata-language-docs/frankly-desired-penguin backport/doc-provisioner-scp/definitely-capital-bug backport/doc-refactoring-nav-link/honestly-sweet-sawfly backport/doc-s3-fix/utterly-close-rabbit backport/docs-fix-typo/usefully-blessed-monkey backport/docs-for-each-list-toset/basically-still-zebra backport/docs/unknwon-value/completely-musical-lionfish backport/f-build-go1.19.3/largely-peaceful-grouper backport/f-cty-funcs-marks/poorly-mutual-walleye backport/f-non-existing-module-instance-crash/neatly-perfect-kiwi backport/f-plan-action-reason/incredibly-concrete-jackal backport/f-plan-refresh-only-and-replace/really-absolute-silkworm backport/file-provisioner-powershell-warning/noticeably-adequate-mullet backport/fix-apt-page/abnormally-relative-quagga backport/fix-backends-link/strangely-emerging-crane backport/fix-backends-link/vertically-noble-hornet backport/fix-broken-link/certainly-measured-chipmunk backport/fix-broken-links-1-10/firmly-equal-rabbit backport/fix-cdktf-link/highly-pumped-snapper backport/fix-glossary-table-contents/uniquely-pro-garfish backport/fix-grammar/precisely-polite-tortoise backport/fix-internals-overview/globally-allowed-kid backport/fix-internals-overview/noticeably-up-rodent backport/fix-intro-page-images/constantly-capable-shiner backport/fix-last-intro-nits/infinitely-workable-redfish backport/fix-links-devdot/strictly-notable-sparrow backport/fix-links-release/seemingly-living-dinosaur backport/fix-remote-backend-references/primarily-tops-mite backport/fix-workspace-name-docs/ideally-uncommon-pheasant backport/jbardin/cancel-auto-approve/extremely-brave-bear backport/jbardin/coerce-value-nested-types/certainly-rested-chigger backport/jbardin/format-empty-nested-attrs/rightly-uncommon-bluegill backport/jbardin/format-id-name-marks/perfectly-boss-bass backport/jbardin/ignore-changes-marks/formerly-new-insect backport/jbardin/k8s-mod-update/likely-probable-falcon backport/jbardin/optional-attrs/obviously-growing-duck backport/jbardin/static-validate-nested-types/possibly-crack-mouse backport/k8s-backend-credentials/radically-decent-mite backport/kevin/rewrite-internal-redirects/quietly-helped-pelican backport/laura-add-mrui-to-sidebar/highly-resolved-blowfish backport/main/absolutely-sterling-whippet backport/main/cleanly-mature-scorpion backport/mg_no_code_prov_followup/marginally-relevant-eagle backport/mktg-tf-76ef54dc3c574e032725e0341be8e1d2/constantly-smart-kingfish backport/mktg-tf-76ef54dc3c574e032725e0341be8e1d2/distinctly-sharp-ferret backport/mktg-tf-76ef54dc3c574e032725e0341be8e1d2/friendly-evident-grouse backport/module-invocation-warning/fully-fitting-buzzard backport/nvanthao/update-docs-implicit-provider/locally-neutral-lemur backport/optional-type-attributes-note/inherently-dear-goat backport/patch-1/gladly-mature-oarfish backport/patch-1/manually-fine-mantis backport/patch-1/nationally-working-kite backport/patch-1/noticeably-comic-manatee backport/patch-1/rarely-informed-gopher backport/patch-1/sensibly-saving-swine backport/patch-1/usually-clear-shad backport/patch-1/vaguely-deciding-beagle backport/patch-1/virtually-more-rhino backport/patch-1/wholly-verified-racer backport/patch-1/willingly-usable-husky backport/patch-1/yearly-rich-skunk backport/patch-2/badly-game-spider backport/patch-2/finally-amazed-catfish backport/patch-2/openly-clean-tick backport/patch-2/weekly-selected-tiger backport/rar-docs-update-add-anchor/cheaply-sincere-escargot backport/remove-future-statement-import/briefly-viable-glowworm backport/remove-provisioners/readily-correct-ferret backport/remove-provisioners/widely-singular-hound backport/replace-flag-clarifications/definitely-saved-elf backport/replace-flag-updates/nominally-assured-weevil backport/startsswith-to-startswith/highly-gorgeous-katydid backport/system-parameter/infinitely-open-bluebird backport/tf-integration-program-guide/briefly-certain-quail backport/tweak-multi-to-multi-migration-tfc/suddenly-real-duckling backport/update-cloud-block-pages/verbally-key-kangaroo backport/update-console-docs/closely-genuine-javelin backport/update-for-each-example/early-crucial-piranha backport/update-plan-page/lightly-outgoing-halibut backport/update-run-task-result/factually-star-sunfish backport/update_docs_for_30072/gradually-trusting-wahoo backport/workspaces-confusion-fixes/secondly-huge-titmouse barrettclark/fix-state-outputs-read-permissions before-sdk-extraction brandonc/changelog_nested_sensitive brandonc/changelog_sensitive_diff_fixes brandonc/cloud_upgrade_013 brandonc/nested_attr_sensitive brandonc/output_cloud_reads brandonc/providers-estimate brandonc/run_variables_types brandonc/scheme_override_cloud brandonc/variable_parsing_refactor build-circleci build-pr-checks build-workflow-dev/cgo-enabled build-workflow-dev/liamcervante/equivalence-test-action bump-gcp-storage-dependency bump-gcp-storage-dependency-2 cgriggs01-stable-pdp cgriggs01-tpdp-default cloud-e2e-fix cloud-integration-changelog-entry cmd-fmt-parens-error dependabot/go_modules/github.com/bmatcuk/doublestar-1.3.4 dependabot/go_modules/github.com/mattn/go-shellwords-1.0.12 dev-portal-updates-docs dividers-devdot-fixes doc-config-refactoring doc-provisioner-scp doc-refactoring-nav-link doc-s3-backend-dynamodb-locks doc-unicode-hcl doc-yamlencode-stable docs-for-each-list-toset docs-readme-updates-versioned-docs docs/top-pages-gloss ds.submodule-nav-main f-addrs-static-checkable f-build-go1.19.3 f-cli-hide-fast-refresh f-cliconfig-hcl2api f-cmd-output-raw f-cmd-show-config f-cmd-web f-cmdline-funcs f-config-refcount f-core-rpc f-cty-funcs-marks f-debugger f-decode-moved f-depfile-pkg f-derived-values-tracking f-diagnostics-cli-reorg f-dynamic-provider-assignment f-e2etest-deps-forbidden f-expand-root-outputs f-exprstress f-fewer-apply-updates f-fileexists-errmsg f-func-cidrsubnets f-func-expandnull f-functions-in-providers f-gen-integration-tests f-implied-move-module-call f-init-provider-source-feedback f-jsonstate-2 f-langserver f-moduletest-2 f-moved-again f-new-build-pipeline f-new-plan-modes f-ng-workflow f-nix-legacy-providers f-objchange-nulls f-output-value-types f-outputs-plan f-partial-plan-on-error f-partial-plan-on-error-ui f-persistent-checks-old f-plan-immediate-ops f-plan-reads f-plan-refresh-only-and-replace f-playground f-plugin-finder f-possible-apply-refactoring f-preconditions-postconditions f-prototype-drift f-prototype-workspaces-are-states f-provider-http-mirrors f-provider-source-local-discovery f-provider-state-storage f-providerinst-command-tests f-providers-mirror f-providers-mirror-2 f-providers-new-installer f-refresh-plan f-resource-for_each-and-experiments f-rpcplugin-interface f-skip-attrs-as-blocks f-state-upgrade-always f-submodule-outputs-inferred-sensitive f-svcauth-environment f-temp-without-provider-blocks f-template-values f-terraform-version-lock f-testing-eval-prototype f-testing-with-conditions f-tfaddr-lib f-unused-attr f-validate-lint f-workspaces2-prototype f/azurerm-backend-msal fix-apt-page fix-broken-link fix-broken-links-1-10 fix-cdktf-link fix-dividers-for-devdot fix-future-facing-language fix-future-lang-2 fix-internals-overview fix-intro-page-images fix-last-intro-nits fix-links-devdot fix-links-release fix-postconditions-example fix-preconditions fix-provisioners-content fix-readme-again fix-value-assertion-errors gcs-backend-add-kms gcs-backend-add-private-connect-support gcs-refactor-credential-handling gitrgoliveira-patch-1 go-get1.5.2 gs/add-pre-plan-run-tasks jan21_language_urls jan21_test_broken_link_check jan21_url_rfc jbardin/1.3-destroy-perf jbardin/backport-29017 jbardin/backport-29167 jbardin/backport-29559 jbardin/backport-31576 jbardin/call-plan-destroy jbardin/data-source-destroy-edges jbardin/go-getter-update jbardin/lookup-objects jbardin/null-variable jbardin/output-perf jbardin/plan-orphan-deleted jbardin/remove-deprecated-backends jbardin/resolved-provided-by jbardin/terraform-data jbardin/terraform-null jbardin/trigger-replacement jbardin/variable-eval json-schema-export-crash july2020_pre0.13_stable-website_backup k8s-backend-credentials k8sback kevin/local-preview-post-split kevin/preview kevin/remove-guides-docs kevin/vercel-config kmoe/http-backend-debug-log kmoe/init-checksum-miss-error kmoe/misc-help-text kmoe/unused-resource-attributes lafentres/autolabel-dependabot-prs lafentres/refactor-show-command laura-fix-jsonencode-note laura-update-docs-readme laura-update-pre-post-conditions liamcervante/cicd-go-vet liamcervante/structured-run-output link_multicloud_tutorial_from_use_cases link_to_refresh_tutorial link_workflow_tutorials megan_invalid_creds megan_update_msg migrate-go-tfe-1_0 mnomitch/varialbe-errors-on-remote-backend nf/nov21-migrate-away-from-cloud nq.content-update nq.docs-content-update omarismail/run-vars p-go-azure-helpers-0.10.0 paddy_backport_proto6_download paddy_module_attribution_tmp paddy_paul_proto52 paddy_protocol_six_registry paddy_tf_log_json pault/0.14-tfce-continue-on-error pluginsdk-v0.12-early1 pluginsdk-v0.12-early2 pluginsdk-v0.12-early3 pluginsdk-v0.12-early4 pluginsdk-v0.12-early5 pluginsdk-v0.12-early6 pluginsdk-v0.12-early7 pluginsdk-v0.12-early8 preapply-runtasks-cli-output preapply-runtasks-clioutput prototype-implicit-graph ps-protocol2 pselle/env_delete rar-docs-update-add-anchor regenerate-protobufs registry-provider-tiers release-notes-env-credentials remote-backend-refresh-alias replace-flag-updates res-add-learn-link-debug res-add-learn-link-debug-1 res-lifecycle-tutorial revamp-cli-config revert-29088-minify_jsonencode review-1.0-docs rln-add-versions-tutorials-links rln-modules-track-reference rt-backport-changelog rt-changelog-entry-1.1 run-tasks-backport sdk-removed sebasslash/add-cloud-e2e-test-workflow sebasslash/add-tf-hostname-env-var sebasslash/add-tf-org-env-var sebasslash/env-cloud-e2e-tests sebasslash/err-approval-input-false sebasslash/resolve-flaky-env-var-test sebasslash/tf-workspace-cloud-config solutiongeek-patch-1 stable-website tags-reconfigure-msg tchupp/override-local-vars test-branch-protection-workflow tf-swift-backend-swauth tfc-integration-docs uk1288/backport-cloud-integration-panic-fix uk1288/fix-for-cloud-integration-panic uk1288/update-changelog-md uk1288/update-changelog-md-v1-1 update-TF-WORKSPACE-variable update-cidrnetmask-docs update-depends-on-docs update-for-each-example update-packaging-action-name update-tfe-login-flow update_gen_meta uturunku1-patch-1 uturunku1-patch-2 v0.12 v0.13 v0.14 v0.15 v011 v1.0 v1.1 vendor-bump-hil v1.4.0-alpha20221109 v1.3.5 v1.3.4 v1.3.3 v1.3.2 v1.3.1 v1.3.0 v1.3.0-rc1 v1.3.0-dev v1.3.0-beta1 v1.3.0-alpha20220817 v1.3.0-alpha20220803 v1.3.0-alpha20220706 v1.3.0-alpha20220622 v1.3.0-alpha20220608 v1.2.9 v1.2.8 v1.2.7 v1.2.6 v1.2.5 v1.2.4 v1.2.3 v1.2.2 v1.2.1 v1.2.0 v1.2.0-rc2 v1.2.0-rc1 v1.2.0-beta1 v1.2.0-alpha20220413 v1.2.0-alpha-20220328 v1.1.9 v1.1.8 v1.1.7 v1.1.6 v1.1.5 v1.1.4 v1.1.3 v1.1.2 v1.1.1 v1.1.0 v1.1.0-rc1 v1.1.0-beta2 v1.1.0-beta1 v1.1.0-alpha20211029 v1.1.0-alpha20211020 v1.1.0-alpha20211006 v1.1.0-alpha20210922 v1.1.0-alpha20210908 v1.1.0-alpha20210811 v1.1.0-alpha20210728 v1.1.0-alpha20210714 v1.1.0-alpha20210630 v1.1.0-alpha20210616 v1.0.11 v1.0.10 v1.0.9 v1.0.8 v1.0.7 v1.0.6 v1.0.5 v1.0.4 v1.0.3 v1.0.2 v1.0.1 v1.0.0 v0.15.5 v0.15.4 v0.15.3 v0.15.2 v0.15.1 v0.15.0 v0.15.0-rc2 v0.15.0-rc1 v0.15.0-beta2 v0.15.0-beta1 v0.15.0-alpha20210210 v0.15.0-alpha20210127 v0.15.0-alpha20210107 v0.14.11 v0.14.10 v0.14.9 v0.14.8 v0.14.7 v0.14.6 v0.14.5 v0.14.4 v0.14.3 v0.14.2 v0.14.1 v0.14.0 v0.14.0-rc1 v0.14.0-beta2 v0.14.0-beta1 v0.14.0-alpha20201007 v0.14.0-alpha20200923 v0.14.0-alpha20200910 v0.13.7 v0.13.6 v0.13.5 v0.13.4 v0.13.3 v0.13.2 v0.13.1 v0.13.0 v0.13.0-rc1 v0.13.0-beta3 v0.13.0-beta2 v0.13.0-beta1 v0.12.31 v0.12.30 v0.12.29 v0.12.28 v0.12.27 v0.12.26 v0.12.25 v0.12.24 v0.12.23 v0.12.22 v0.12.21 v0.12.20 v0.12.19 v0.12.18 v0.12.17 v0.12.16 v0.12.15 v0.12.14 v0.12.13 v0.12.12 v0.12.11 v0.12.10 v0.12.9 v0.12.8 v0.12.7 v0.12.6 v0.12.5 v0.12.4 v0.12.3 v0.12.2 v0.12.1 v0.12.0 v0.12.0-rc1 v0.12.0-dev20190520H16 v0.12.0-beta2 v0.12.0-beta1 v0.12.0-alpha4 v0.12.0-alpha3
No related merge requests found
Showing with 429 additions and 5 deletions
+429 -5
......@@ -28,6 +28,7 @@ type State struct {
}
var _ statemgr.Full = (*State)(nil)
var _ statemgr.Migrator = (*State)(nil)
// statemgr.Reader impl.
func (s *State) State() *states.State {
......@@ -37,6 +38,14 @@ func (s *State) State() *states.State {
return s.state.DeepCopy()
}
// StateForMigration is part of our implementation of statemgr.Migrator.
func (s *State) StateForMigration() *statefile.File {
s.mu.Lock()
defer s.mu.Unlock()
return statefile.New(s.state.DeepCopy(), s.lineage, s.serial)
}
// statemgr.Writer impl.
func (s *State) WriteState(state *states.State) error {
s.mu.Lock()
......@@ -50,6 +59,28 @@ func (s *State) WriteState(state *states.State) error {
return nil
}
// WriteStateForMigration is part of our implementation of statemgr.Migrator.
func (s *State) WriteStateForMigration(f *statefile.File, force bool) error {
s.mu.Lock()
defer s.mu.Unlock()
checkFile := statefile.New(s.state, s.lineage, s.serial)
if !force {
if err := statemgr.CheckValidImport(f, checkFile); err != nil {
return err
}
}
// We create a deep copy of the state here, because the caller also has
// a reference to the given object and can potentially go on to mutate
// it after we return, but we want the snapshot at this point in time.
s.state = f.State.DeepCopy()
s.lineage = f.Lineage
s.serial = f.Serial
return nil
}
// statemgr.Refresher impl.
func (s *State) RefreshState() error {
s.mu.Lock()
......
......@@ -6,6 +6,7 @@ import (
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"sync"
......@@ -62,6 +63,7 @@ type Filesystem struct {
var (
_ Full = (*Filesystem)(nil)
_ PersistentMeta = (*Filesystem)(nil)
_ Migrator = (*Filesystem)(nil)
)
// NewFilesystem creates a filesystem-based state manager that reads and writes
......@@ -121,9 +123,6 @@ func (s *Filesystem) State() *states.State {
// WriteState is an incorrect implementation of Writer that actually also
// persists.
// WriteState for LocalState always persists the state as well.
//
// StateWriter impl.
func (s *Filesystem) WriteState(state *states.State) error {
// TODO: this should use a more robust method of writing state, by first
// writing to a temp file on the same filesystem, and renaming the file over
......@@ -137,7 +136,10 @@ func (s *Filesystem) WriteState(state *states.State) error {
}
defer s.mutex()()
return s.writeState(state, nil)
}
func (s *Filesystem) writeState(state *states.State, meta *SnapshotMeta) error {
// We'll try to write our backup first, so we can be sure we've created
// it successfully before clobbering the original file it came from.
if !s.writtenBackup && s.backupFile != nil && s.backupPath != "" && !statefile.StatesMarshalEqual(state, s.backupFile.State) {
......@@ -180,8 +182,14 @@ func (s *Filesystem) WriteState(state *states.State) error {
return nil
}
if s.readFile == nil || !statefile.StatesMarshalEqual(s.file.State, s.readFile.State) {
s.file.Serial++
if meta == nil {
if s.readFile == nil || !statefile.StatesMarshalEqual(s.file.State, s.readFile.State) {
s.file.Serial++
}
} else {
// Force new metadata
s.file.Lineage = meta.Lineage
s.file.Serial = meta.Serial
}
if err := statefile.Write(s.file, s.stateFileOut); err != nil {
......@@ -345,6 +353,51 @@ func (s *Filesystem) StateSnapshotMeta() SnapshotMeta {
}
}
// StateForMigration is part of our implementation of Migrator.
func (s *Filesystem) StateForMigration() *statefile.File {
return s.file.DeepCopy()
}
// WriteStateForMigration is part of our implementation of Migrator.
func (s *Filesystem) WriteStateForMigration(f *statefile.File, force bool) error {
if s.readFile == nil {
err := s.RefreshState()
if err != nil {
return err
}
}
defer s.mutex()()
if !force {
err := CheckValidImport(f, s.readFile)
if err != nil {
return err
}
}
if s.readFile != nil {
log.Printf(
"[TRACE] statemgr.Filesystem: Importing snapshot with lineage %q serial %d over snapshot with lineage %q serial %d at %s",
f.Lineage, f.Serial,
s.readFile.Lineage, s.readFile.Serial,
s.path,
)
} else {
log.Printf(
"[TRACE] statemgr.Filesystem: Importing snapshot with lineage %q serial %d as the initial state snapshot at %s",
f.Lineage, f.Serial,
s.path,
)
}
err := s.writeState(f.State, &SnapshotMeta{Lineage: f.Lineage, Serial: f.Serial})
if err != nil {
return err
}
return nil
}
// Open the state file, creating the directories and file as needed.
func (s *Filesystem) createStateFiles() error {
......
package statemgr
import (
"fmt"
"github.com/hashicorp/terraform/states/statefile"
)
// Migrator is an optional interface implemented by state managers that
// are capable of direct migration of state snapshots with their associated
// metadata unchanged.
//
// This interface is used when available by function Migrate. See that
// function for more information on how it is used.
type Migrator interface {
PersistentMeta
// StateForMigration returns a full statefile representing the latest
// snapshot (as would be returned by Reader.State) and the associated
// snapshot metadata (as would be returned by
// PersistentMeta.StateSnapshotMeta).
//
// Just as with Reader.State, this must not fail.
StateForMigration() *statefile.File
// WriteStateForMigration accepts a full statefile including associated
// snapshot metadata, and atomically updates the stored file (as with
// Writer.WriteState) and the metadata.
//
// If "force" is not set, the manager must call CheckValidImport with
// the given file and the current file and complete the update only if
// that function returns nil. If force is set this may override such
// checks, but some backends do not support forcing and so will act
// as if force is always true.
WriteStateForMigration(f *statefile.File, force bool) error
}
// Migrate writes the latest transient state snapshot from src into dest,
// preserving snapshot metadata (serial and lineage) where possible.
//
// If both managers implement the optional interface Migrator then it will
// be used to copy the snapshot and its associated metadata. Otherwise,
// the normal Reader and Writer interfaces will be used instead.
//
// If the destination manager refuses the new state or fails to write it then
// its error is returned directly.
//
// For state managers that also implement Persistent, it is the caller's
// responsibility to persist the newly-written state after a successful result,
// just as with calls to Writer.WriteState.
//
// This function doesn't do any locking of its own, so if the state managers
// also implement Locker the caller should hold a lock on both managers
// for the duration of this call.
func Migrate(dst, src Transient) error {
if dstM, ok := dst.(Migrator); ok {
if srcM, ok := src.(Migrator); ok {
// Full-fidelity migration, them.
s := srcM.StateForMigration()
return dstM.WriteStateForMigration(s, true)
}
}
// Managers to not support full-fidelity migration, so migration will not
// preserve serial/lineage.
s := src.State()
return dst.WriteState(s)
}
// Import loads the given state snapshot into the given manager, preserving
// its metadata (serial and lineage) if the target manager supports metadata.
//
// A state manager must implement the optional interface Migrator to get
// access to the full metadata.
//
// Unless "force" is true, Import will check first that the metadata given
// in the file matches the current snapshot metadata for the manager, if the
// manager supports metadata. Some managers do not support forcing, so a
// write with an unsuitable lineage or serial may still be rejected even if
// "force" is set. "force" has no effect for managers that do not support
// snapshot metadata.
//
// For state managers that also implement Persistent, it is the caller's
// responsibility to persist the newly-written state after a successful result,
// just as with calls to Writer.WriteState.
//
// This function doesn't do any locking of its own, so if the state manager
// also implements Locker the caller should hold a lock on it for the
// duration of this call.
func Import(f *statefile.File, mgr Transient, force bool) error {
if mgrM, ok := mgr.(Migrator); ok {
return mgrM.WriteStateForMigration(f, force)
}
// For managers that don't implement Migrator, this is just a normal write
// of the state contained in the given file.
return mgr.WriteState(f.State)
}
// Export retrieves the latest state snapshot from the given manager, including
// its metadata (serial and lineage) where possible.
//
// A state manager must also implement either Migrator or PersistentMeta
// for the metadata to be included. Otherwise, the relevant fields will have
// zero value in the returned object.
//
// For state managers that also implement Persistent, it is the caller's
// responsibility to refresh from persistent storage first if needed.
//
// This function doesn't do any locking of its own, so if the state manager
// also implements Locker the caller should hold a lock on it for the
// duration of this call.
func Export(mgr Reader) *statefile.File {
switch mgrT := mgr.(type) {
case Migrator:
return mgrT.StateForMigration()
case PersistentMeta:
s := mgr.State()
meta := mgrT.StateSnapshotMeta()
return statefile.New(s, meta.Lineage, meta.Serial)
default:
s := mgr.State()
return statefile.New(s, "", 0)
}
}
// SnapshotMetaRel describes a relationship between two SnapshotMeta values,
// returned from the SnapshotMeta.Compare method where the "first" value
// is the receiver of that method and the "second" is the given argument.
type SnapshotMetaRel rune
//go:generate stringer -type=SnapshotMetaRel
const (
// SnapshotOlder indicates that two snapshots have a common lineage and
// that the first has a lower serial value.
SnapshotOlder SnapshotMetaRel = '<'
// SnapshotNewer indicates that two snapshots have a common lineage and
// that the first has a higher serial value.
SnapshotNewer SnapshotMetaRel = '>'
// SnapshotEqual indicates that two snapshots have a common lineage and
// the same serial value.
SnapshotEqual SnapshotMetaRel = '='
// SnapshotUnrelated indicates that two snapshots have different lineage
// and thus cannot be meaningfully compared.
SnapshotUnrelated SnapshotMetaRel = '!'
// SnapshotLegacy indicates that one or both of the snapshots
// does not have a lineage at all, and thus no comparison is possible.
SnapshotLegacy SnapshotMetaRel = '?'
)
// Compare determines the relationship, if any, between the given existing
// SnapshotMeta and the potential "new" SnapshotMeta that is the receiver.
func (m SnapshotMeta) Compare(existing SnapshotMeta) SnapshotMetaRel {
switch {
case m.Lineage == "" || existing.Lineage == "":
return SnapshotLegacy
case m.Lineage != existing.Lineage:
return SnapshotUnrelated
case m.Serial > existing.Serial:
return SnapshotNewer
case m.Serial < existing.Serial:
return SnapshotOlder
default:
// both serials are equal, by elimination
return SnapshotEqual
}
}
// CheckValidImport returns nil if the "new" snapshot can be imported as a
// successor of the "existing" snapshot without forcing.
//
// If not, an error is returned describing why.
func CheckValidImport(newFile, existingFile *statefile.File) error {
if existingFile == nil || existingFile.State.Empty() {
// It's always okay to overwrite an empty state, regardless of
// its lineage/serial.
return nil
}
new := SnapshotMeta{
Lineage: newFile.Lineage,
Serial: newFile.Serial,
}
existing := SnapshotMeta{
Lineage: existingFile.Lineage,
Serial: existingFile.Serial,
}
rel := new.Compare(existing)
switch rel {
case SnapshotNewer:
return nil // a newer snapshot is fine
case SnapshotLegacy:
return nil // anything goes for a legacy state
case SnapshotUnrelated:
return fmt.Errorf("cannot import state with lineage %q over unrelated state with lineage %q", new.Lineage, existing.Lineage)
case SnapshotEqual:
if statefile.StatesMarshalEqual(newFile.State, existingFile.State) {
// If lineage, serial, and state all match then this is fine.
return nil
}
return fmt.Errorf("cannot overwrite existing state with serial %d with a different state that has the same serial", new.Serial)
case SnapshotOlder:
return fmt.Errorf("cannot import state with serial %d over newer state with serial %d", new.Serial, existing.Serial)
default:
// Should never happen, but we'll check to make sure for safety
return fmt.Errorf("unsupported state snapshot relationship %s", rel)
}
}
package statemgr
import (
"testing"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/states/statefile"
)
func TestCheckValidImport(t *testing.T) {
barState := states.BuildState(func(s *states.SyncState) {
s.SetOutputValue(
addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance),
cty.StringVal("bar"), false,
)
})
notBarState := states.BuildState(func(s *states.SyncState) {
s.SetOutputValue(
addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance),
cty.StringVal("not bar"), false,
)
})
emptyState := states.NewState()
tests := map[string]struct {
New *statefile.File
Existing *statefile.File
WantErr string
}{
"exact match": {
New: statefile.New(barState, "lineage", 1),
Existing: statefile.New(barState, "lineage", 1),
WantErr: ``,
},
"overwrite unrelated empty state": {
New: statefile.New(barState, "lineage1", 1),
Existing: statefile.New(emptyState, "lineage2", 1),
WantErr: ``,
},
"different state with same serial": {
New: statefile.New(barState, "lineage", 1),
Existing: statefile.New(notBarState, "lineage", 1),
WantErr: `cannot overwrite existing state with serial 1 with a different state that has the same serial`,
},
"different state with newer serial": {
New: statefile.New(barState, "lineage", 2),
Existing: statefile.New(notBarState, "lineage", 1),
WantErr: ``,
},
"different state with older serial": {
New: statefile.New(barState, "lineage", 1),
Existing: statefile.New(notBarState, "lineage", 2),
WantErr: `cannot import state with serial 1 over newer state with serial 2`,
},
"different lineage with same serial": {
New: statefile.New(barState, "lineage1", 2),
Existing: statefile.New(notBarState, "lineage2", 2),
WantErr: `cannot import state with lineage "lineage1" over unrelated state with lineage "lineage2"`,
},
"different lineage with different serial": {
New: statefile.New(barState, "lineage1", 3),
Existing: statefile.New(notBarState, "lineage2", 2),
WantErr: `cannot import state with lineage "lineage1" over unrelated state with lineage "lineage2"`,
},
"new state is legacy": {
New: statefile.New(barState, "", 2),
Existing: statefile.New(notBarState, "lineage", 2),
WantErr: ``,
},
"old state is legacy": {
New: statefile.New(barState, "lineage", 2),
Existing: statefile.New(notBarState, "", 2),
WantErr: ``,
},
"both states are legacy": {
New: statefile.New(barState, "", 2),
Existing: statefile.New(notBarState, "", 2),
WantErr: ``,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
gotErr := CheckValidImport(test.New, test.Existing)
if test.WantErr == "" {
if gotErr != nil {
t.Errorf("unexpected error: %s", gotErr)
}
} else {
if gotErr == nil {
t.Errorf("succeeded, but want error: %s", test.WantErr)
} else if got, want := gotErr.Error(), test.WantErr; got != want {
t.Errorf("wrong error\ngot: %s\nwant: %s", got, want)
}
}
})
}
}
// Code generated by "stringer -type=SnapshotMetaRel"; DO NOT EDIT.
package statemgr
import "strconv"
const (
_SnapshotMetaRel_name_0 = "SnapshotUnrelated"
_SnapshotMetaRel_name_1 = "SnapshotOlderSnapshotEqualSnapshotNewerSnapshotLegacy"
)
var (
_SnapshotMetaRel_index_1 = [...]uint8{0, 13, 26, 39, 53}
)
func (i SnapshotMetaRel) String() string {
switch {
case i == 33:
return _SnapshotMetaRel_name_0
case 60 <= i && i <= 63:
i -= 60
return _SnapshotMetaRel_name_1[_SnapshotMetaRel_index_1[i]:_SnapshotMetaRel_index_1[i+1]]
default:
return "SnapshotMetaRel(" + strconv.FormatInt(int64(i), 10) + ")"
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment