@@ -46,7 +46,7 @@ func (m *terranovaApplyMutator) Apply(ctx context.Context, b *bundle.Bundle) dia
4646
4747 for _ , action := range b .Plan .Actions {
4848 node := nodeKey {action .Group , action .Name }
49- plannedActionsMap [nodeKey { action . Group , action . Name } ] = action .ActionType
49+ plannedActionsMap [node ] = action .ActionType
5050 if ! g .HasNode (node ) {
5151 if action .ActionType == deployplan .ActionTypeDelete {
5252 // it is expected that this node is not seen by makeResourceGraph
@@ -69,21 +69,29 @@ func (m *terranovaApplyMutator) Apply(ctx context.Context, b *bundle.Bundle) dia
6969
7070 client := b .WorkspaceClient ()
7171
72- g .Run (defaultParallelism , func (node nodeKey ) {
73- // TODO: if a given node fails, all downstream nodes should not be run. We should report those nodes.
72+ g .Run (defaultParallelism , func (node nodeKey , failedDependency * nodeKey ) bool {
73+ actionType := plannedActionsMap [node ]
74+
75+ errorPrefix := fmt .Sprintf ("cannot %s %s.%s" , actionType .String (), node .Group , node .Name )
76+
77+ // If a dependency failed, report and skip execution for this node by returning false
78+ if failedDependency != nil {
79+ logdiag .LogError (ctx , fmt .Errorf ("%s: dependency failed: %s" , errorPrefix , failedDependency .String ()))
80+ return false
81+ }
82+
7483 // TODO: ensure that config for this node is fully resolved at this point.
7584
7685 settings , ok := SupportedResources [node .Group ]
7786 if ! ok {
78- return
87+ // Unexpected, this should be filtered at plan.
88+ return false
7989 }
8090
81- actionType := plannedActionsMap [node ]
82-
8391 // The way plan currently works, is that it does not add resources with Noop action, turning them into Unset.
8492 // So we skip both, although at this point we will not see Noop here.
8593 if actionType == deployplan .ActionTypeUnset || actionType == deployplan .ActionTypeNoop {
86- return
94+ return true
8795 }
8896
8997 d := Deployer {
@@ -97,55 +105,59 @@ func (m *terranovaApplyMutator) Apply(ctx context.Context, b *bundle.Bundle) dia
97105 if actionType == deployplan .ActionTypeDelete {
98106 err = d .destroy (ctx )
99107 if err != nil {
100- logdiag .LogError (ctx , fmt .Errorf ("destroying %s.%s: %w" , d .group , d .resourceName , err ))
108+ logdiag .LogError (ctx , fmt .Errorf ("%s: %w" , errorPrefix , err ))
109+ return false
101110 }
102- return
111+ return true
103112 }
104113
105114 config , ok := b .GetResourceConfig (node .Group , node .Name )
106115 if ! ok {
107- logdiag .LogError (ctx , fmt .Errorf ("internal error when reading config for %s.%s " , node . Group , node . Name ))
108- return
116+ logdiag .LogError (ctx , fmt .Errorf ("%s: internal error when reading config" , errorPrefix ))
117+ return false
109118 }
110119
111120 // Fetch the references to ensure all are resolved
112121 myReferences , err := extractReferences (b .Config .Value (), node )
113122 if err != nil {
114- logdiag .LogError (ctx , err )
115- return
123+ logdiag .LogError (ctx , fmt . Errorf ( "%s: reading references from config: %w" , errorPrefix , err ) )
124+ return false
116125 }
117126
118127 // At this point it's an error to have unresolved deps
119128 if len (myReferences ) > 0 {
120129 // TODO: include the deps themselves in the message
121- logdiag .LogError (ctx , fmt .Errorf ("cannot deploy %s.%s due to unresolved deps" , node . Group , node . Name ))
122- return
130+ logdiag .LogError (ctx , fmt .Errorf ("%s: unresolved deps" , errorPrefix ))
131+ return false
123132 }
124133
125- // TODO: redo plan to downgrade planned action if possible (?)
134+ // TODO: redo calcDiff to downgrade planned action if possible (?)
126135
127136 err = d .Deploy (ctx , config , actionType )
128137 if err != nil {
129- logdiag .LogError (ctx , err )
130- return
138+ logdiag .LogError (ctx , fmt . Errorf ( "%s: %w" , errorPrefix , err ) )
139+ return false
131140 }
132141
142+ // Update resources.id after successful deploy so that future ${resources...id} refs are replaced
133143 if isReferenced [node ] {
134- err = resolveIDReference (ctx , b , d . group , d . resourceName )
144+ err = resolveIDReference (ctx , b , node . Group , node . Name )
135145 if err != nil {
136- logdiag .LogError (ctx , fmt .Errorf ("failed to replace ref to resources.%s.%s.id: %w" , d .group , d .resourceName , err ))
137- return
146+ // not using errorPrefix because resource was deployed
147+ logdiag .LogError (ctx , fmt .Errorf ("failed to replace ref to resources.%s.%s.id: %w" , node .Group , node .Name , err ))
148+ return false
138149 }
139150 }
151+
152+ return true
140153 })
141154
155+ // This must run even if deploy failed:
142156 err = b .ResourceDatabase .Finalize ()
143157 if err != nil {
144158 logdiag .LogError (ctx , err )
145159 }
146160
147- // TODO: check if all planned actions were performed
148-
149161 return nil
150162}
151163
@@ -157,18 +169,10 @@ type Deployer struct {
157169 settings ResourceSettings
158170}
159171
160- func (d * Deployer ) Deploy (ctx context.Context , inputConfig any , actionType deployplan.ActionType ) error {
161- err := d .deploy (ctx , inputConfig , actionType )
162- if err != nil {
163- return fmt .Errorf ("deploying %s.%s: %w" , d .group , d .resourceName , err )
164- }
165- return nil
166- }
167-
168172func (d * Deployer ) destroy (ctx context.Context ) error {
169173 entry , hasEntry := d .db .GetResourceEntry (d .group , d .resourceName )
170174 if ! hasEntry {
171- log .Infof (ctx , "%s.%s: Cannot delete, missing from state" , d .group , d .resourceName )
175+ log .Infof (ctx , "Cannot delete %s.%s: missing from state" , d .group , d .resourceName )
172176 return nil
173177 }
174178
@@ -184,7 +188,7 @@ func (d *Deployer) destroy(ctx context.Context) error {
184188 return nil
185189}
186190
187- func (d * Deployer ) deploy (ctx context.Context , inputConfig any , actionType deployplan.ActionType ) error {
191+ func (d * Deployer ) Deploy (ctx context.Context , inputConfig any , actionType deployplan.ActionType ) error {
188192 resource , _ , err := New (d .client , d .group , d .resourceName , inputConfig )
189193 if err != nil {
190194 return err
@@ -218,14 +222,15 @@ func (d *Deployer) deploy(ctx context.Context, inputConfig any, actionType deplo
218222 }
219223 return d .UpdateWithID (ctx , resource , updater , oldID , config )
220224 default :
221- return fmt .Errorf ("internal error: unexpected plan : %#v" , actionType )
225+ return fmt .Errorf ("internal error: unexpected actionType : %#v" , actionType )
222226 }
223227}
224228
225229func (d * Deployer ) Create (ctx context.Context , resource IResource , config any ) error {
226230 newID , err := resource .DoCreate (ctx )
227231 if err != nil {
228- return fmt .Errorf ("creating: %w" , err )
232+ // No need to prefix error, there is no ambiguity (only one operation - DoCreate) and no additional context (like id)
233+ return err
229234 }
230235
231236 log .Infof (ctx , "Created %s.%s id=%#v" , d .group , d .resourceName , newID )
@@ -260,7 +265,7 @@ func (d *Deployer) Recreate(ctx context.Context, resource IResource, oldID strin
260265 }
261266
262267 // TODO: This should be at notice level (info < notice < warn) and it should be visible by default,
263- // but to match terraform output today, we hide it.
268+ // but to match terraform output today, we hide it (and also we don't have notice level)
264269 log .Infof (ctx , "Recreated %s.%s id=%#v (previously %#v)" , d .group , d .resourceName , newID , oldID )
265270 err = d .db .SaveState (d .group , d .resourceName , newID , config )
266271 if err != nil {
0 commit comments