github.com/opentofu/opentofu@v1.7.1/internal/plans/changes.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package plans
     7  
     8  import (
     9  	"github.com/zclconf/go-cty/cty"
    10  
    11  	"github.com/opentofu/opentofu/internal/addrs"
    12  	"github.com/opentofu/opentofu/internal/states"
    13  )
    14  
    15  // Changes describes various actions that OpenTofu will attempt to take if
    16  // the corresponding plan is applied.
    17  //
    18  // A Changes object can be rendered into a visual diff (by the caller, using
    19  // code in another package) for display to the user.
    20  type Changes struct {
    21  	// Resources tracks planned changes to resource instance objects.
    22  	Resources []*ResourceInstanceChangeSrc
    23  
    24  	// Outputs tracks planned changes output values.
    25  	//
    26  	// Note that although an in-memory plan contains planned changes for
    27  	// outputs throughout the configuration, a plan serialized
    28  	// to disk retains only the root outputs because they are
    29  	// externally-visible, while other outputs are implementation details and
    30  	// can be easily re-calculated during the apply phase. Therefore only root
    31  	// module outputs will survive a round-trip through a plan file.
    32  	Outputs []*OutputChangeSrc
    33  }
    34  
    35  // NewChanges returns a valid Changes object that describes no changes.
    36  func NewChanges() *Changes {
    37  	return &Changes{}
    38  }
    39  
    40  func (c *Changes) Empty() bool {
    41  	for _, res := range c.Resources {
    42  		if res.Action != NoOp || res.Moved() {
    43  			return false
    44  		}
    45  
    46  		if res.Importing != nil {
    47  			return false
    48  		}
    49  	}
    50  
    51  	for _, out := range c.Outputs {
    52  		if out.Addr.Module.IsRoot() && out.Action != NoOp {
    53  			return false
    54  		}
    55  	}
    56  
    57  	return true
    58  }
    59  
    60  // ResourceInstance returns the planned change for the current object of the
    61  // resource instance of the given address, if any. Returns nil if no change is
    62  // planned.
    63  func (c *Changes) ResourceInstance(addr addrs.AbsResourceInstance) *ResourceInstanceChangeSrc {
    64  	for _, rc := range c.Resources {
    65  		if rc.Addr.Equal(addr) && rc.DeposedKey == states.NotDeposed {
    66  			return rc
    67  		}
    68  	}
    69  
    70  	return nil
    71  
    72  }
    73  
    74  // InstancesForAbsResource returns the planned change for the current objects
    75  // of the resource instances of the given address, if any. Returns nil if no
    76  // changes are planned.
    77  func (c *Changes) InstancesForAbsResource(addr addrs.AbsResource) []*ResourceInstanceChangeSrc {
    78  	var changes []*ResourceInstanceChangeSrc
    79  	for _, rc := range c.Resources {
    80  		resAddr := rc.Addr.ContainingResource()
    81  		if resAddr.Equal(addr) && rc.DeposedKey == states.NotDeposed {
    82  			changes = append(changes, rc)
    83  		}
    84  	}
    85  
    86  	return changes
    87  }
    88  
    89  // InstancesForConfigResource returns the planned change for the current objects
    90  // of the resource instances of the given address, if any. Returns nil if no
    91  // changes are planned.
    92  func (c *Changes) InstancesForConfigResource(addr addrs.ConfigResource) []*ResourceInstanceChangeSrc {
    93  	var changes []*ResourceInstanceChangeSrc
    94  	for _, rc := range c.Resources {
    95  		resAddr := rc.Addr.ContainingResource().Config()
    96  		if resAddr.Equal(addr) && rc.DeposedKey == states.NotDeposed {
    97  			changes = append(changes, rc)
    98  		}
    99  	}
   100  
   101  	return changes
   102  }
   103  
   104  // ResourceInstanceDeposed returns the plan change of a deposed object of
   105  // the resource instance of the given address, if any. Returns nil if no change
   106  // is planned.
   107  func (c *Changes) ResourceInstanceDeposed(addr addrs.AbsResourceInstance, key states.DeposedKey) *ResourceInstanceChangeSrc {
   108  	for _, rc := range c.Resources {
   109  		if rc.Addr.Equal(addr) && rc.DeposedKey == key {
   110  			return rc
   111  		}
   112  	}
   113  
   114  	return nil
   115  }
   116  
   117  // OutputValue returns the planned change for the output value with the
   118  //
   119  //	given address, if any. Returns nil if no change is planned.
   120  func (c *Changes) OutputValue(addr addrs.AbsOutputValue) *OutputChangeSrc {
   121  	for _, oc := range c.Outputs {
   122  		if oc.Addr.Equal(addr) {
   123  			return oc
   124  		}
   125  	}
   126  
   127  	return nil
   128  }
   129  
   130  // RootOutputValues returns planned changes for all outputs of the root module.
   131  func (c *Changes) RootOutputValues() []*OutputChangeSrc {
   132  	var res []*OutputChangeSrc
   133  
   134  	for _, oc := range c.Outputs {
   135  		// we can't evaluate root module outputs
   136  		if !oc.Addr.Module.Equal(addrs.RootModuleInstance) {
   137  			continue
   138  		}
   139  
   140  		res = append(res, oc)
   141  
   142  	}
   143  
   144  	return res
   145  }
   146  
   147  // OutputValues returns planned changes for all outputs for all module
   148  // instances that reside in the parent path.  Returns nil if no changes are
   149  // planned.
   150  func (c *Changes) OutputValues(parent addrs.ModuleInstance, module addrs.ModuleCall) []*OutputChangeSrc {
   151  	var res []*OutputChangeSrc
   152  
   153  	for _, oc := range c.Outputs {
   154  		// we can't evaluate root module outputs
   155  		if oc.Addr.Module.Equal(addrs.RootModuleInstance) {
   156  			continue
   157  		}
   158  
   159  		changeMod, changeCall := oc.Addr.Module.Call()
   160  		// this does not reside on our parent instance path
   161  		if !changeMod.Equal(parent) {
   162  			continue
   163  		}
   164  
   165  		// this is not the module you're looking for
   166  		if changeCall.Name != module.Name {
   167  			continue
   168  		}
   169  
   170  		res = append(res, oc)
   171  
   172  	}
   173  
   174  	return res
   175  }
   176  
   177  // SyncWrapper returns a wrapper object around the receiver that can be used
   178  // to make certain changes to the receiver in a concurrency-safe way, as long
   179  // as all callers share the same wrapper object.
   180  func (c *Changes) SyncWrapper() *ChangesSync {
   181  	return &ChangesSync{
   182  		changes: c,
   183  	}
   184  }
   185  
   186  // ResourceInstanceChange describes a change to a particular resource instance
   187  // object.
   188  type ResourceInstanceChange struct {
   189  	// Addr is the absolute address of the resource instance that the change
   190  	// will apply to.
   191  	Addr addrs.AbsResourceInstance
   192  
   193  	// PrevRunAddr is the absolute address that this resource instance had at
   194  	// the conclusion of a previous run.
   195  	//
   196  	// This will typically be the same as Addr, but can be different if the
   197  	// previous resource instance was subject to a "moved" block that we
   198  	// handled in the process of creating this plan.
   199  	//
   200  	// For the initial creation of a resource instance there isn't really any
   201  	// meaningful "previous run address", but PrevRunAddr will still be set
   202  	// equal to Addr in that case in order to simplify logic elsewhere which
   203  	// aims to detect and react to the movement of instances between addresses.
   204  	PrevRunAddr addrs.AbsResourceInstance
   205  
   206  	// DeposedKey is the identifier for a deposed object associated with the
   207  	// given instance, or states.NotDeposed if this change applies to the
   208  	// current object.
   209  	//
   210  	// A Replace change for a resource with create_before_destroy set will
   211  	// create a new DeposedKey temporarily during replacement. In that case,
   212  	// DeposedKey in the plan is always states.NotDeposed, representing that
   213  	// the current object is being replaced with the deposed.
   214  	DeposedKey states.DeposedKey
   215  
   216  	// Provider is the address of the provider configuration that was used
   217  	// to plan this change, and thus the configuration that must also be
   218  	// used to apply it.
   219  	ProviderAddr addrs.AbsProviderConfig
   220  
   221  	// Change is an embedded description of the change.
   222  	Change
   223  
   224  	// ActionReason is an optional extra indication of why we chose the
   225  	// action recorded in Change.Action for this particular resource instance.
   226  	//
   227  	// This is an approximate mechanism only for the purpose of explaining the
   228  	// plan to end-users in the UI and is not to be used for any
   229  	// decision-making during the apply step; if apply behavior needs to vary
   230  	// depending on the "action reason" then the information for that decision
   231  	// must be recorded more precisely elsewhere for that purpose.
   232  	//
   233  	// Sometimes there might be more than one reason for choosing a particular
   234  	// action. In that case, it's up to the codepath making that decision to
   235  	// decide which value would provide the most relevant explanation to the
   236  	// end-user and return that. It's not a goal of this field to represent
   237  	// fine details about the planning process.
   238  	ActionReason ResourceInstanceChangeActionReason
   239  
   240  	// RequiredReplace is a set of paths that caused the change action to be
   241  	// Replace rather than Update. Always nil if the change action is not
   242  	// Replace.
   243  	//
   244  	// This is retained only for UI-plan-rendering purposes and so it does not
   245  	// currently survive a round-trip through a saved plan file.
   246  	RequiredReplace cty.PathSet
   247  
   248  	// Private allows a provider to stash any extra data that is opaque to
   249  	// OpenTofu that relates to this change. OpenTofu will save this
   250  	// byte-for-byte and return it to the provider in the apply call.
   251  	Private []byte
   252  }
   253  
   254  // Encode produces a variant of the reciever that has its change values
   255  // serialized so it can be written to a plan file. Pass the implied type of the
   256  // corresponding resource type schema for correct operation.
   257  func (rc *ResourceInstanceChange) Encode(ty cty.Type) (*ResourceInstanceChangeSrc, error) {
   258  	cs, err := rc.Change.Encode(ty)
   259  	if err != nil {
   260  		return nil, err
   261  	}
   262  	prevRunAddr := rc.PrevRunAddr
   263  	if prevRunAddr.Resource.Resource.Type == "" {
   264  		// Suggests an old caller that hasn't been properly updated to
   265  		// populate this yet.
   266  		prevRunAddr = rc.Addr
   267  	}
   268  	return &ResourceInstanceChangeSrc{
   269  		Addr:            rc.Addr,
   270  		PrevRunAddr:     prevRunAddr,
   271  		DeposedKey:      rc.DeposedKey,
   272  		ProviderAddr:    rc.ProviderAddr,
   273  		ChangeSrc:       *cs,
   274  		ActionReason:    rc.ActionReason,
   275  		RequiredReplace: rc.RequiredReplace,
   276  		Private:         rc.Private,
   277  	}, err
   278  }
   279  
   280  func (rc *ResourceInstanceChange) Moved() bool {
   281  	return !rc.Addr.Equal(rc.PrevRunAddr)
   282  }
   283  
   284  // Simplify will, where possible, produce a change with a simpler action than
   285  // the receiever given a flag indicating whether the caller is dealing with
   286  // a normal apply or a destroy. This flag deals with the fact that OpenTofu
   287  // Core uses a specialized graph node type for destroying; only that
   288  // specialized node should set "destroying" to true.
   289  //
   290  // The following table shows the simplification behavior:
   291  //
   292  //	Action    Destroying?   New Action
   293  //	--------+-------------+-----------
   294  //	Create    true          NoOp
   295  //	Delete    false         NoOp
   296  //	Replace   true          Delete
   297  //	Replace   false         Create
   298  //
   299  // For any combination not in the above table, the Simplify just returns the
   300  // receiver as-is.
   301  func (rc *ResourceInstanceChange) Simplify(destroying bool) *ResourceInstanceChange {
   302  	if destroying {
   303  		switch rc.Action {
   304  		case Delete:
   305  			// We'll fall out and just return rc verbatim, then.
   306  		case CreateThenDelete, DeleteThenCreate:
   307  			return &ResourceInstanceChange{
   308  				Addr:         rc.Addr,
   309  				DeposedKey:   rc.DeposedKey,
   310  				Private:      rc.Private,
   311  				ProviderAddr: rc.ProviderAddr,
   312  				Change: Change{
   313  					Action:          Delete,
   314  					Before:          rc.Before,
   315  					After:           cty.NullVal(rc.Before.Type()),
   316  					Importing:       rc.Importing,
   317  					GeneratedConfig: rc.GeneratedConfig,
   318  				},
   319  			}
   320  		default:
   321  			return &ResourceInstanceChange{
   322  				Addr:         rc.Addr,
   323  				DeposedKey:   rc.DeposedKey,
   324  				Private:      rc.Private,
   325  				ProviderAddr: rc.ProviderAddr,
   326  				Change: Change{
   327  					Action:          NoOp,
   328  					Before:          rc.Before,
   329  					After:           rc.Before,
   330  					Importing:       rc.Importing,
   331  					GeneratedConfig: rc.GeneratedConfig,
   332  				},
   333  			}
   334  		}
   335  	} else {
   336  		switch rc.Action {
   337  		case Delete:
   338  			return &ResourceInstanceChange{
   339  				Addr:         rc.Addr,
   340  				DeposedKey:   rc.DeposedKey,
   341  				Private:      rc.Private,
   342  				ProviderAddr: rc.ProviderAddr,
   343  				Change: Change{
   344  					Action:          NoOp,
   345  					Before:          rc.Before,
   346  					After:           rc.Before,
   347  					Importing:       rc.Importing,
   348  					GeneratedConfig: rc.GeneratedConfig,
   349  				},
   350  			}
   351  		case CreateThenDelete, DeleteThenCreate:
   352  			return &ResourceInstanceChange{
   353  				Addr:         rc.Addr,
   354  				DeposedKey:   rc.DeposedKey,
   355  				Private:      rc.Private,
   356  				ProviderAddr: rc.ProviderAddr,
   357  				Change: Change{
   358  					Action:          Create,
   359  					Before:          cty.NullVal(rc.After.Type()),
   360  					After:           rc.After,
   361  					Importing:       rc.Importing,
   362  					GeneratedConfig: rc.GeneratedConfig,
   363  				},
   364  			}
   365  		}
   366  	}
   367  
   368  	// If we fall out here then our change is already simple enough.
   369  	return rc
   370  }
   371  
   372  // ResourceInstanceChangeActionReason allows for some extra user-facing
   373  // reasoning for why a particular change action was chosen for a particular
   374  // resource instance.
   375  //
   376  // This only represents sufficient detail to give a suitable explanation to
   377  // an end-user, and mustn't be used for any real decision-making during the
   378  // apply step.
   379  type ResourceInstanceChangeActionReason rune
   380  
   381  //go:generate go run golang.org/x/tools/cmd/stringer -type=ResourceInstanceChangeActionReason changes.go
   382  
   383  const (
   384  	// In most cases there's no special reason for choosing a particular
   385  	// action, which is represented by ResourceInstanceChangeNoReason.
   386  	ResourceInstanceChangeNoReason ResourceInstanceChangeActionReason = 0
   387  
   388  	// ResourceInstanceReplaceBecauseTainted indicates that the resource
   389  	// instance must be replaced because its existing current object is
   390  	// marked as "tainted".
   391  	ResourceInstanceReplaceBecauseTainted ResourceInstanceChangeActionReason = 'T'
   392  
   393  	// ResourceInstanceReplaceByRequest indicates that the resource instance
   394  	// is planned to be replaced because a caller specifically asked for it
   395  	// to be using ReplaceAddrs. (On the command line, the -replace=...
   396  	// planning option.)
   397  	ResourceInstanceReplaceByRequest ResourceInstanceChangeActionReason = 'R'
   398  
   399  	// ResourceInstanceReplaceByTriggers indicates that the resource instance
   400  	// is planned to be replaced because of a corresponding change in a
   401  	// replace_triggered_by reference.
   402  	ResourceInstanceReplaceByTriggers ResourceInstanceChangeActionReason = 'D'
   403  
   404  	// ResourceInstanceReplaceBecauseCannotUpdate indicates that the resource
   405  	// instance is planned to be replaced because the provider has indicated
   406  	// that a requested change cannot be applied as an update.
   407  	//
   408  	// In this case, the RequiredReplace field will typically be populated on
   409  	// the ResourceInstanceChange object to give information about specifically
   410  	// which arguments changed in a non-updatable way.
   411  	ResourceInstanceReplaceBecauseCannotUpdate ResourceInstanceChangeActionReason = 'F'
   412  
   413  	// ResourceInstanceDeleteBecauseNoResourceConfig indicates that the
   414  	// resource instance is planned to be deleted because there's no
   415  	// corresponding resource configuration block in the configuration.
   416  	ResourceInstanceDeleteBecauseNoResourceConfig ResourceInstanceChangeActionReason = 'N'
   417  
   418  	// ResourceInstanceDeleteBecauseWrongRepetition indicates that the
   419  	// resource instance is planned to be deleted because the instance key
   420  	// type isn't consistent with the repetition mode selected in the
   421  	// resource configuration.
   422  	ResourceInstanceDeleteBecauseWrongRepetition ResourceInstanceChangeActionReason = 'W'
   423  
   424  	// ResourceInstanceDeleteBecauseCountIndex indicates that the resource
   425  	// instance is planned to be deleted because its integer instance key
   426  	// is out of range for the current configured resource "count" value.
   427  	ResourceInstanceDeleteBecauseCountIndex ResourceInstanceChangeActionReason = 'C'
   428  
   429  	// ResourceInstanceDeleteBecauseEachKey indicates that the resource
   430  	// instance is planned to be deleted because its string instance key
   431  	// isn't one of the keys included in the current configured resource
   432  	// "for_each" value.
   433  	ResourceInstanceDeleteBecauseEachKey ResourceInstanceChangeActionReason = 'E'
   434  
   435  	// ResourceInstanceDeleteBecauseNoModule indicates that the resource
   436  	// instance is planned to be deleted because it belongs to a module
   437  	// instance that's no longer declared in the configuration.
   438  	//
   439  	// This is less specific than the reasons we return for the various ways
   440  	// a resource instance itself can be no longer declared, including both
   441  	// the total removal of a module block and changes to its count/for_each
   442  	// arguments. This difference in detail is out of pragmatism, because
   443  	// potentially multiple nested modules could all contribute conflicting
   444  	// specific reasons for a particular instance to no longer be declared.
   445  	ResourceInstanceDeleteBecauseNoModule ResourceInstanceChangeActionReason = 'M'
   446  
   447  	// ResourceInstanceDeleteBecauseNoMoveTarget indicates that the resource
   448  	// address appears as the target ("to") in a moved block, but no
   449  	// configuration exists for that resource. According to our move rules,
   450  	// this combination evaluates to a deletion of the "new" resource.
   451  	ResourceInstanceDeleteBecauseNoMoveTarget ResourceInstanceChangeActionReason = 'A'
   452  
   453  	// ResourceInstanceReadBecauseConfigUnknown indicates that the resource
   454  	// must be read during apply (rather than during planning) because its
   455  	// configuration contains unknown values. This reason applies only to
   456  	// data resources.
   457  	ResourceInstanceReadBecauseConfigUnknown ResourceInstanceChangeActionReason = '?'
   458  
   459  	// ResourceInstanceReadBecauseDependencyPending indicates that the resource
   460  	// must be read during apply (rather than during planning) because it
   461  	// depends on a managed resource instance which has its own changes
   462  	// pending.
   463  	ResourceInstanceReadBecauseDependencyPending ResourceInstanceChangeActionReason = '!'
   464  
   465  	// ResourceInstanceReadBecauseCheckNested indicates that the resource must
   466  	// be read during apply (as well as during planning) because it is inside
   467  	// a check block and when the check assertions execute we want them to use
   468  	// the most up-to-date data.
   469  	ResourceInstanceReadBecauseCheckNested ResourceInstanceChangeActionReason = '#'
   470  )
   471  
   472  // OutputChange describes a change to an output value.
   473  type OutputChange struct {
   474  	// Addr is the absolute address of the output value that the change
   475  	// will apply to.
   476  	Addr addrs.AbsOutputValue
   477  
   478  	// Change is an embedded description of the change.
   479  	//
   480  	// For output value changes, the type constraint for the DynamicValue
   481  	// instances is always cty.DynamicPseudoType.
   482  	Change
   483  
   484  	// Sensitive, if true, indicates that either the old or new value in the
   485  	// change is sensitive and so a rendered version of the plan in the UI
   486  	// should elide the actual values while still indicating the action of the
   487  	// change.
   488  	Sensitive bool
   489  }
   490  
   491  // Encode produces a variant of the reciever that has its change values
   492  // serialized so it can be written to a plan file.
   493  func (oc *OutputChange) Encode() (*OutputChangeSrc, error) {
   494  	cs, err := oc.Change.Encode(cty.DynamicPseudoType)
   495  	if err != nil {
   496  		return nil, err
   497  	}
   498  	return &OutputChangeSrc{
   499  		Addr:      oc.Addr,
   500  		ChangeSrc: *cs,
   501  		Sensitive: oc.Sensitive,
   502  	}, err
   503  }
   504  
   505  // Importing is the part of a ChangeSrc that describes the embedded import
   506  // action.
   507  //
   508  // The fields in here are subject to change, so downstream consumers should be
   509  // prepared for backwards compatibility in case the contents changes.
   510  type Importing struct {
   511  	// ID is the original ID of the imported resource.
   512  	ID string
   513  }
   514  
   515  // Change describes a single change with a given action.
   516  type Change struct {
   517  	// Action defines what kind of change is being made.
   518  	Action Action
   519  
   520  	// Interpretation of Before and After depend on Action:
   521  	//
   522  	//     NoOp     Before and After are the same, unchanged value
   523  	//     Create   Before is nil, and After is the expected value after create.
   524  	//     Read     Before is any prior value (nil if no prior), and After is the
   525  	//              value that was or will be read.
   526  	//     Update   Before is the value prior to update, and After is the expected
   527  	//              value after update.
   528  	//     Replace  As with Update.
   529  	//     Delete   Before is the value prior to delete, and After is always nil.
   530  	//
   531  	// Unknown values may appear anywhere within the Before and After values,
   532  	// either as the values themselves or as nested elements within known
   533  	// collections/structures.
   534  	Before, After cty.Value
   535  
   536  	// Importing is present if the resource is being imported as part of this
   537  	// change.
   538  	//
   539  	// Use the simple presence of this field to detect if a ChangeSrc is to be
   540  	// imported, the contents of this structure may be modified going forward.
   541  	Importing *Importing
   542  
   543  	// GeneratedConfig contains any HCL config generated for this resource
   544  	// during planning, as a string. If GeneratedConfig is populated, Importing
   545  	// should be true. However, not all Importing changes contain generated
   546  	// config.
   547  	GeneratedConfig string
   548  }
   549  
   550  // Encode produces a variant of the reciever that has its change values
   551  // serialized so it can be written to a plan file. Pass the type constraint
   552  // that the values are expected to conform to; to properly decode the values
   553  // later an identical type constraint must be provided at that time.
   554  //
   555  // Where a Change is embedded in some other struct, it's generally better
   556  // to call the corresponding Encode method of that struct rather than working
   557  // directly with its embedded Change.
   558  func (c *Change) Encode(ty cty.Type) (*ChangeSrc, error) {
   559  	// Storing unmarked values so that we can encode unmarked values
   560  	// and save the PathValueMarks for re-marking the values later
   561  	var beforeVM, afterVM []cty.PathValueMarks
   562  	unmarkedBefore := c.Before
   563  	unmarkedAfter := c.After
   564  
   565  	if c.Before.ContainsMarked() {
   566  		unmarkedBefore, beforeVM = c.Before.UnmarkDeepWithPaths()
   567  	}
   568  	beforeDV, err := NewDynamicValue(unmarkedBefore, ty)
   569  	if err != nil {
   570  		return nil, err
   571  	}
   572  
   573  	if c.After.ContainsMarked() {
   574  		unmarkedAfter, afterVM = c.After.UnmarkDeepWithPaths()
   575  	}
   576  	afterDV, err := NewDynamicValue(unmarkedAfter, ty)
   577  	if err != nil {
   578  		return nil, err
   579  	}
   580  
   581  	var importing *ImportingSrc
   582  	if c.Importing != nil {
   583  		importing = &ImportingSrc{ID: c.Importing.ID}
   584  	}
   585  
   586  	return &ChangeSrc{
   587  		Action:          c.Action,
   588  		Before:          beforeDV,
   589  		After:           afterDV,
   590  		BeforeValMarks:  beforeVM,
   591  		AfterValMarks:   afterVM,
   592  		Importing:       importing,
   593  		GeneratedConfig: c.GeneratedConfig,
   594  	}, nil
   595  }