github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/command/views/json/hook.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package json 5 6 import ( 7 "fmt" 8 "time" 9 10 "github.com/terramate-io/tf/addrs" 11 "github.com/terramate-io/tf/plans" 12 ) 13 14 type Hook interface { 15 HookType() MessageType 16 String() string 17 } 18 19 // ApplyStart: triggered by PreApply hook 20 type applyStart struct { 21 Resource ResourceAddr `json:"resource"` 22 Action ChangeAction `json:"action"` 23 IDKey string `json:"id_key,omitempty"` 24 IDValue string `json:"id_value,omitempty"` 25 actionVerb string 26 } 27 28 var _ Hook = (*applyStart)(nil) 29 30 func (h *applyStart) HookType() MessageType { 31 return MessageApplyStart 32 } 33 34 func (h *applyStart) String() string { 35 var id string 36 if h.IDKey != "" && h.IDValue != "" { 37 id = fmt.Sprintf(" [%s=%s]", h.IDKey, h.IDValue) 38 } 39 return fmt.Sprintf("%s: %s...%s", h.Resource.Addr, h.actionVerb, id) 40 } 41 42 func NewApplyStart(addr addrs.AbsResourceInstance, action plans.Action, idKey string, idValue string) Hook { 43 hook := &applyStart{ 44 Resource: newResourceAddr(addr), 45 Action: changeAction(action), 46 IDKey: idKey, 47 IDValue: idValue, 48 actionVerb: startActionVerb(action), 49 } 50 51 return hook 52 } 53 54 // ApplyProgress: currently triggered by a timer started on PreApply. In 55 // future, this might also be triggered by provider progress reporting. 56 type applyProgress struct { 57 Resource ResourceAddr `json:"resource"` 58 Action ChangeAction `json:"action"` 59 Elapsed float64 `json:"elapsed_seconds"` 60 actionVerb string 61 elapsed time.Duration 62 } 63 64 var _ Hook = (*applyProgress)(nil) 65 66 func (h *applyProgress) HookType() MessageType { 67 return MessageApplyProgress 68 } 69 70 func (h *applyProgress) String() string { 71 return fmt.Sprintf("%s: Still %s... [%s elapsed]", h.Resource.Addr, h.actionVerb, h.elapsed) 72 } 73 74 func NewApplyProgress(addr addrs.AbsResourceInstance, action plans.Action, elapsed time.Duration) Hook { 75 return &applyProgress{ 76 Resource: newResourceAddr(addr), 77 Action: changeAction(action), 78 Elapsed: elapsed.Seconds(), 79 actionVerb: progressActionVerb(action), 80 elapsed: elapsed, 81 } 82 } 83 84 // ApplyComplete: triggered by PostApply hook 85 type applyComplete struct { 86 Resource ResourceAddr `json:"resource"` 87 Action ChangeAction `json:"action"` 88 IDKey string `json:"id_key,omitempty"` 89 IDValue string `json:"id_value,omitempty"` 90 Elapsed float64 `json:"elapsed_seconds"` 91 actionNoun string 92 elapsed time.Duration 93 } 94 95 var _ Hook = (*applyComplete)(nil) 96 97 func (h *applyComplete) HookType() MessageType { 98 return MessageApplyComplete 99 } 100 101 func (h *applyComplete) String() string { 102 var id string 103 if h.IDKey != "" && h.IDValue != "" { 104 id = fmt.Sprintf(" [%s=%s]", h.IDKey, h.IDValue) 105 } 106 return fmt.Sprintf("%s: %s complete after %s%s", h.Resource.Addr, h.actionNoun, h.elapsed, id) 107 } 108 109 func NewApplyComplete(addr addrs.AbsResourceInstance, action plans.Action, idKey, idValue string, elapsed time.Duration) Hook { 110 return &applyComplete{ 111 Resource: newResourceAddr(addr), 112 Action: changeAction(action), 113 IDKey: idKey, 114 IDValue: idValue, 115 Elapsed: elapsed.Seconds(), 116 actionNoun: actionNoun(action), 117 elapsed: elapsed, 118 } 119 } 120 121 // ApplyErrored: triggered by PostApply hook on failure. This will be followed 122 // by diagnostics when the apply finishes. 123 type applyErrored struct { 124 Resource ResourceAddr `json:"resource"` 125 Action ChangeAction `json:"action"` 126 Elapsed float64 `json:"elapsed_seconds"` 127 actionNoun string 128 elapsed time.Duration 129 } 130 131 var _ Hook = (*applyErrored)(nil) 132 133 func (h *applyErrored) HookType() MessageType { 134 return MessageApplyErrored 135 } 136 137 func (h *applyErrored) String() string { 138 return fmt.Sprintf("%s: %s errored after %s", h.Resource.Addr, h.actionNoun, h.elapsed) 139 } 140 141 func NewApplyErrored(addr addrs.AbsResourceInstance, action plans.Action, elapsed time.Duration) Hook { 142 return &applyErrored{ 143 Resource: newResourceAddr(addr), 144 Action: changeAction(action), 145 Elapsed: elapsed.Seconds(), 146 actionNoun: actionNoun(action), 147 elapsed: elapsed, 148 } 149 } 150 151 // ProvisionStart: triggered by PreProvisionInstanceStep hook 152 type provisionStart struct { 153 Resource ResourceAddr `json:"resource"` 154 Provisioner string `json:"provisioner"` 155 } 156 157 var _ Hook = (*provisionStart)(nil) 158 159 func (h *provisionStart) HookType() MessageType { 160 return MessageProvisionStart 161 } 162 163 func (h *provisionStart) String() string { 164 return fmt.Sprintf("%s: Provisioning with '%s'...", h.Resource.Addr, h.Provisioner) 165 } 166 167 func NewProvisionStart(addr addrs.AbsResourceInstance, provisioner string) Hook { 168 return &provisionStart{ 169 Resource: newResourceAddr(addr), 170 Provisioner: provisioner, 171 } 172 } 173 174 // ProvisionProgress: triggered by ProvisionOutput hook 175 type provisionProgress struct { 176 Resource ResourceAddr `json:"resource"` 177 Provisioner string `json:"provisioner"` 178 Output string `json:"output"` 179 } 180 181 var _ Hook = (*provisionProgress)(nil) 182 183 func (h *provisionProgress) HookType() MessageType { 184 return MessageProvisionProgress 185 } 186 187 func (h *provisionProgress) String() string { 188 return fmt.Sprintf("%s: (%s): %s", h.Resource.Addr, h.Provisioner, h.Output) 189 } 190 191 func NewProvisionProgress(addr addrs.AbsResourceInstance, provisioner string, output string) Hook { 192 return &provisionProgress{ 193 Resource: newResourceAddr(addr), 194 Provisioner: provisioner, 195 Output: output, 196 } 197 } 198 199 // ProvisionComplete: triggered by PostProvisionInstanceStep hook 200 type provisionComplete struct { 201 Resource ResourceAddr `json:"resource"` 202 Provisioner string `json:"provisioner"` 203 } 204 205 var _ Hook = (*provisionComplete)(nil) 206 207 func (h *provisionComplete) HookType() MessageType { 208 return MessageProvisionComplete 209 } 210 211 func (h *provisionComplete) String() string { 212 return fmt.Sprintf("%s: (%s) Provisioning complete", h.Resource.Addr, h.Provisioner) 213 } 214 215 func NewProvisionComplete(addr addrs.AbsResourceInstance, provisioner string) Hook { 216 return &provisionComplete{ 217 Resource: newResourceAddr(addr), 218 Provisioner: provisioner, 219 } 220 } 221 222 // ProvisionErrored: triggered by PostProvisionInstanceStep hook on failure. 223 // This will be followed by diagnostics when the apply finishes. 224 type provisionErrored struct { 225 Resource ResourceAddr `json:"resource"` 226 Provisioner string `json:"provisioner"` 227 } 228 229 var _ Hook = (*provisionErrored)(nil) 230 231 func (h *provisionErrored) HookType() MessageType { 232 return MessageProvisionErrored 233 } 234 235 func (h *provisionErrored) String() string { 236 return fmt.Sprintf("%s: (%s) Provisioning errored", h.Resource.Addr, h.Provisioner) 237 } 238 239 func NewProvisionErrored(addr addrs.AbsResourceInstance, provisioner string) Hook { 240 return &provisionErrored{ 241 Resource: newResourceAddr(addr), 242 Provisioner: provisioner, 243 } 244 } 245 246 // RefreshStart: triggered by PreRefresh hook 247 type refreshStart struct { 248 Resource ResourceAddr `json:"resource"` 249 IDKey string `json:"id_key,omitempty"` 250 IDValue string `json:"id_value,omitempty"` 251 } 252 253 var _ Hook = (*refreshStart)(nil) 254 255 func (h *refreshStart) HookType() MessageType { 256 return MessageRefreshStart 257 } 258 259 func (h *refreshStart) String() string { 260 var id string 261 if h.IDKey != "" && h.IDValue != "" { 262 id = fmt.Sprintf(" [%s=%s]", h.IDKey, h.IDValue) 263 } 264 return fmt.Sprintf("%s: Refreshing state...%s", h.Resource.Addr, id) 265 } 266 267 func NewRefreshStart(addr addrs.AbsResourceInstance, idKey, idValue string) Hook { 268 return &refreshStart{ 269 Resource: newResourceAddr(addr), 270 IDKey: idKey, 271 IDValue: idValue, 272 } 273 } 274 275 // RefreshComplete: triggered by PostRefresh hook 276 type refreshComplete struct { 277 Resource ResourceAddr `json:"resource"` 278 IDKey string `json:"id_key,omitempty"` 279 IDValue string `json:"id_value,omitempty"` 280 } 281 282 var _ Hook = (*refreshComplete)(nil) 283 284 func (h *refreshComplete) HookType() MessageType { 285 return MessageRefreshComplete 286 } 287 288 func (h *refreshComplete) String() string { 289 var id string 290 if h.IDKey != "" && h.IDValue != "" { 291 id = fmt.Sprintf(" [%s=%s]", h.IDKey, h.IDValue) 292 } 293 return fmt.Sprintf("%s: Refresh complete%s", h.Resource.Addr, id) 294 } 295 296 func NewRefreshComplete(addr addrs.AbsResourceInstance, idKey, idValue string) Hook { 297 return &refreshComplete{ 298 Resource: newResourceAddr(addr), 299 IDKey: idKey, 300 IDValue: idValue, 301 } 302 } 303 304 // Convert the subset of plans.Action values we expect to receive into a 305 // present-tense verb for the applyStart hook message. 306 func startActionVerb(action plans.Action) string { 307 switch action { 308 case plans.Create: 309 return "Creating" 310 case plans.Update: 311 return "Modifying" 312 case plans.Delete: 313 return "Destroying" 314 case plans.Read: 315 return "Refreshing" 316 case plans.CreateThenDelete, plans.DeleteThenCreate: 317 // This is not currently possible to reach, as we receive separate 318 // passes for create and delete 319 return "Replacing" 320 case plans.NoOp: 321 // This should never be possible: a no-op planned change should not 322 // be applied. We'll fall back to "Applying". 323 fallthrough 324 default: 325 return "Applying" 326 } 327 } 328 329 // Convert the subset of plans.Action values we expect to receive into a 330 // present-tense verb for the applyProgress hook message. This will be 331 // prefixed with "Still ", so it is lower-case. 332 func progressActionVerb(action plans.Action) string { 333 switch action { 334 case plans.Create: 335 return "creating" 336 case plans.Update: 337 return "modifying" 338 case plans.Delete: 339 return "destroying" 340 case plans.Read: 341 return "refreshing" 342 case plans.CreateThenDelete, plans.DeleteThenCreate: 343 // This is not currently possible to reach, as we receive separate 344 // passes for create and delete 345 return "replacing" 346 case plans.NoOp: 347 // This should never be possible: a no-op planned change should not 348 // be applied. We'll fall back to "applying". 349 fallthrough 350 default: 351 return "applying" 352 } 353 } 354 355 // Convert the subset of plans.Action values we expect to receive into a 356 // noun for the applyComplete and applyErrored hook messages. This will be 357 // combined into a phrase like "Creation complete after 1m4s". 358 func actionNoun(action plans.Action) string { 359 switch action { 360 case plans.Create: 361 return "Creation" 362 case plans.Update: 363 return "Modifications" 364 case plans.Delete: 365 return "Destruction" 366 case plans.Read: 367 return "Refresh" 368 case plans.CreateThenDelete, plans.DeleteThenCreate: 369 // This is not currently possible to reach, as we receive separate 370 // passes for create and delete 371 return "Replacement" 372 case plans.NoOp: 373 // This should never be possible: a no-op planned change should not 374 // be applied. We'll fall back to "Apply". 375 fallthrough 376 default: 377 return "Apply" 378 } 379 }