@@ -3,19 +3,25 @@ package aws
33import (
44 "fmt"
55 "log"
6+ "strconv"
67 "time"
78
89 "github.com/aws/aws-sdk-go/aws"
910 "github.com/aws/aws-sdk-go/service/applicationautoscaling"
11+ "github.com/hashicorp/aws-sdk-go-base/tfawserr"
1012 "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
1113 "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1214 "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
15+ "github.com/terraform-providers/terraform-provider-aws/aws/internal/experimental/nullable"
16+ "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/applicationautoscaling/finder"
17+ "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource"
1318)
1419
1520func resourceAwsAppautoscalingScheduledAction () * schema.Resource {
1621 return & schema.Resource {
1722 Create : resourceAwsAppautoscalingScheduledActionPut ,
1823 Read : resourceAwsAppautoscalingScheduledActionRead ,
24+ Update : resourceAwsAppautoscalingScheduledActionPut ,
1925 Delete : resourceAwsAppautoscalingScheduledActionDelete ,
2026
2127 Schema : map [string ]* schema.Schema {
@@ -36,54 +42,57 @@ func resourceAwsAppautoscalingScheduledAction() *schema.Resource {
3642 },
3743 "scalable_dimension" : {
3844 Type : schema .TypeString ,
39- Optional : true ,
45+ Required : true ,
4046 ForceNew : true ,
4147 },
4248 "scalable_target_action" : {
4349 Type : schema .TypeList ,
44- Optional : true ,
45- ForceNew : true ,
50+ Required : true ,
4651 MaxItems : 1 ,
4752 Elem : & schema.Resource {
4853 Schema : map [string ]* schema.Schema {
4954 "max_capacity" : {
50- Type : schema .TypeInt ,
51- Optional : true ,
52- ForceNew : true ,
55+ Type : nullable .TypeNullableInt ,
56+ Optional : true ,
57+ ValidateFunc : nullable .ValidateTypeStringNullableIntAtLeast (0 ),
58+ AtLeastOneOf : []string {
59+ "scalable_target_action.0.max_capacity" ,
60+ "scalable_target_action.0.min_capacity" ,
61+ },
5362 },
5463 "min_capacity" : {
55- Type : schema .TypeInt ,
56- Optional : true ,
57- ForceNew : true ,
64+ Type : nullable .TypeNullableInt ,
65+ Optional : true ,
66+ ValidateFunc : nullable .ValidateTypeStringNullableIntAtLeast (0 ),
67+ AtLeastOneOf : []string {
68+ "scalable_target_action.0.max_capacity" ,
69+ "scalable_target_action.0.min_capacity" ,
70+ },
5871 },
5972 },
6073 },
6174 },
6275 "schedule" : {
6376 Type : schema .TypeString ,
64- Optional : true ,
65- ForceNew : true ,
77+ Required : true ,
6678 },
6779 // The AWS API normalizes start_time and end_time to UTC. Uses
6880 // suppressEquivalentTime to allow any timezone to be used.
6981 "start_time" : {
7082 Type : schema .TypeString ,
7183 Optional : true ,
72- ForceNew : true ,
7384 ValidateFunc : validation .IsRFC3339Time ,
7485 DiffSuppressFunc : suppressEquivalentTime ,
7586 },
7687 "end_time" : {
7788 Type : schema .TypeString ,
7889 Optional : true ,
79- ForceNew : true ,
8090 ValidateFunc : validation .IsRFC3339Time ,
8191 DiffSuppressFunc : suppressEquivalentTime ,
8292 },
8393 "timezone" : {
8494 Type : schema .TypeString ,
8595 Optional : true ,
86- ForceNew : true ,
8796 Default : "UTC" ,
8897 },
8998 "arn" : {
@@ -101,109 +110,119 @@ func resourceAwsAppautoscalingScheduledActionPut(d *schema.ResourceData, meta in
101110 ScheduledActionName : aws .String (d .Get ("name" ).(string )),
102111 ServiceNamespace : aws .String (d .Get ("service_namespace" ).(string )),
103112 ResourceId : aws .String (d .Get ("resource_id" ).(string )),
104- Timezone : aws .String (d .Get ("timezone" ).(string )),
105- }
106- if v , ok := d .GetOk ("scalable_dimension" ); ok {
107- input .ScalableDimension = aws .String (v .(string ))
108- }
109- if v , ok := d .GetOk ("schedule" ); ok {
110- input .Schedule = aws .String (v .(string ))
113+ ScalableDimension : aws .String (d .Get ("scalable_dimension" ).(string )),
111114 }
112- if v , ok := d .GetOk ("scalable_target_action" ); ok {
113- sta := & applicationautoscaling.ScalableTargetAction {}
114- raw := v .([]interface {})[0 ].(map [string ]interface {})
115- if max , ok := raw ["max_capacity" ]; ok {
116- sta .MaxCapacity = aws .Int64 (int64 (max .(int )))
117- }
118- if min , ok := raw ["min_capacity" ]; ok {
119- sta .MinCapacity = aws .Int64 (int64 (min .(int )))
120- }
121- input .ScalableTargetAction = sta
115+
116+ needsPut := true
117+ if d .IsNewResource () {
118+ appautoscalingScheduledActionPopulateInputForCreate (input , d )
119+ } else {
120+ needsPut = appautoscalingScheduledActionPopulateInputForUpdate (input , d )
122121 }
123- if v , ok := d .GetOk ("start_time" ); ok {
124- t , err := time .Parse (time .RFC3339 , v .(string ))
125- if err != nil {
126- return fmt .Errorf ("Error Parsing Appautoscaling Scheduled Action Start Time: %w" , err )
122+
123+ if needsPut {
124+ err := resource .Retry (5 * time .Minute , func () * resource.RetryError {
125+ _ , err := conn .PutScheduledAction (input )
126+ if err != nil {
127+ if tfawserr .ErrCodeEquals (err , applicationautoscaling .ErrCodeObjectNotFoundException ) {
128+ return resource .RetryableError (err )
129+ }
130+ return resource .NonRetryableError (err )
131+ }
132+ return nil
133+ })
134+ if isResourceTimeoutError (err ) {
135+ _ , err = conn .PutScheduledAction (input )
127136 }
128- input .StartTime = aws .Time (t )
129- }
130- if v , ok := d .GetOk ("end_time" ); ok {
131- t , err := time .Parse (time .RFC3339 , v .(string ))
132137 if err != nil {
133- return fmt .Errorf ("Error Parsing Appautoscaling Scheduled Action End Time : %w" , err )
138+ return fmt .Errorf ("error putting Application Auto Scaling scheduled action : %w" , err )
134139 }
135- input .EndTime = aws .Time (t )
136- }
137140
138- err := resource .Retry (5 * time .Minute , func () * resource.RetryError {
139- _ , err := conn .PutScheduledAction (input )
140- if err != nil {
141- if isAWSErr (err , applicationautoscaling .ErrCodeObjectNotFoundException , "" ) {
142- return resource .RetryableError (err )
143- }
144- return resource .NonRetryableError (err )
141+ if d .IsNewResource () {
142+ d .SetId (d .Get ("name" ).(string ) + "-" + d .Get ("service_namespace" ).(string ) + "-" + d .Get ("resource_id" ).(string ))
145143 }
146- return nil
147- })
148- if isResourceTimeoutError (err ) {
149- _ , err = conn .PutScheduledAction (input )
150144 }
151145
152- if err != nil {
153- return fmt .Errorf ("error putting scheduled action: %w" , err )
154- }
155-
156- d .SetId (d .Get ("name" ).(string ) + "-" + d .Get ("service_namespace" ).(string ) + "-" + d .Get ("resource_id" ).(string ))
157146 return resourceAwsAppautoscalingScheduledActionRead (d , meta )
158147}
159148
160- func resourceAwsAppautoscalingScheduledActionRead (d * schema.ResourceData , meta interface {}) error {
161- conn := meta .(* AWSClient ).appautoscalingconn
149+ func appautoscalingScheduledActionPopulateInputForCreate (input * applicationautoscaling.PutScheduledActionInput , d * schema.ResourceData ) {
150+ input .Schedule = aws .String (d .Get ("schedule" ).(string ))
151+ input .ScalableTargetAction = expandScalableTargetAction (d .Get ("scalable_target_action" ).([]interface {}))
152+ input .Timezone = aws .String (d .Get ("timezone" ).(string ))
162153
163- saName := d .Get ("name" ).(string )
164- input := & applicationautoscaling.DescribeScheduledActionsInput {
165- ResourceId : aws .String (d .Get ("resource_id" ).(string )),
166- ScheduledActionNames : []* string {aws .String (saName )},
167- ServiceNamespace : aws .String (d .Get ("service_namespace" ).(string )),
154+ if v , ok := d .GetOk ("start_time" ); ok {
155+ t , _ := time .Parse (time .RFC3339 , v .(string ))
156+ input .StartTime = aws .Time (t )
168157 }
169- resp , err := conn . DescribeScheduledActions ( input )
170- if err != nil {
171- return fmt . Errorf ( "error describing Application Auto Scaling Scheduled Action (%s): %w" , d . Id (), err )
158+ if v , ok := d . GetOk ( "end_time" ); ok {
159+ t , _ := time . Parse ( time . RFC3339 , v .( string ))
160+ input . EndTime = aws . Time ( t )
172161 }
162+ }
173163
174- var scheduledAction * applicationautoscaling.ScheduledAction
164+ func appautoscalingScheduledActionPopulateInputForUpdate (input * applicationautoscaling.PutScheduledActionInput , d * schema.ResourceData ) bool {
165+ hasChange := false
175166
176- if resp == nil {
177- return fmt .Errorf ("error describing Application Auto Scaling Scheduled Action (%s): empty response" , d .Id ())
167+ if d .HasChange ("schedule" ) {
168+ input .Schedule = aws .String (d .Get ("schedule" ).(string ))
169+ hasChange = true
178170 }
179171
180- for _ , sa := range resp .ScheduledActions {
181- if sa == nil {
182- continue
183- }
172+ if d .HasChange ("scalable_target_action" ) {
173+ input .ScalableTargetAction = expandScalableTargetAction (d .Get ("scalable_target_action" ).([]interface {}))
174+ hasChange = true
175+ }
176+
177+ if d .HasChange ("timezone" ) {
178+ input .Timezone = aws .String (d .Get ("timezone" ).(string ))
179+ hasChange = true
180+ }
184181
185- if aws .StringValue (sa .ScheduledActionName ) == saName {
186- scheduledAction = sa
187- break
182+ if d .HasChange ("start_time" ) {
183+ if v , ok := d .GetOk ("start_time" ); ok {
184+ t , _ := time .Parse (time .RFC3339 , v .(string ))
185+ input .StartTime = aws .Time (t )
186+ hasChange = true
187+ }
188+ }
189+ if d .HasChange ("end_time" ) {
190+ if v , ok := d .GetOk ("end_time" ); ok {
191+ t , _ := time .Parse (time .RFC3339 , v .(string ))
192+ input .EndTime = aws .Time (t )
193+ hasChange = true
188194 }
189195 }
190196
191- if scheduledAction == nil {
192- log .Printf ("[WARN] Application Autoscaling Scheduled Action (%s) not found, removing from state" , d .Id ())
197+ return hasChange
198+ }
199+
200+ func resourceAwsAppautoscalingScheduledActionRead (d * schema.ResourceData , meta interface {}) error {
201+ conn := meta .(* AWSClient ).appautoscalingconn
202+
203+ scheduledAction , err := finder .ScheduledAction (conn , d .Get ("name" ).(string ), d .Get ("service_namespace" ).(string ), d .Get ("resource_id" ).(string ))
204+ if tfresource .NotFound (err ) {
205+ log .Printf ("[WARN] Application Auto Scaling Scheduled Action (%s) not found, removing from state" , d .Id ())
193206 d .SetId ("" )
194207 return nil
195208 }
209+ if err != nil {
210+ return fmt .Errorf ("error describing Application Auto Scaling Scheduled Action (%s): %w" , d .Id (), err )
211+ }
196212
197- d .Set ("arn" , scheduledAction .ScheduledActionARN )
213+ if err := d .Set ("scalable_target_action" , flattenScalableTargetAction (scheduledAction .ScalableTargetAction )); err != nil {
214+ return fmt .Errorf ("error setting scalable_target_action: %w" , err )
215+ }
198216
217+ d .Set ("schedule" , scheduledAction .Schedule )
199218 if scheduledAction .StartTime != nil {
200219 d .Set ("start_time" , scheduledAction .StartTime .Format (time .RFC3339 ))
201220 }
202221 if scheduledAction .EndTime != nil {
203222 d .Set ("end_time" , scheduledAction .EndTime .Format (time .RFC3339 ))
204223 }
205-
206224 d .Set ("timezone" , scheduledAction .Timezone )
225+ d .Set ("arn" , scheduledAction .ScheduledActionARN )
207226
208227 return nil
209228}
@@ -221,12 +240,51 @@ func resourceAwsAppautoscalingScheduledActionDelete(d *schema.ResourceData, meta
221240 }
222241 _ , err := conn .DeleteScheduledAction (input )
223242 if err != nil {
224- if isAWSErr (err , applicationautoscaling .ErrCodeObjectNotFoundException , "" ) {
225- log .Printf ("[WARN] Application Autoscaling Scheduled Action (%s) already gone , removing from state" , d .Id ())
243+ if tfawserr . ErrCodeEquals (err , applicationautoscaling .ErrCodeObjectNotFoundException ) {
244+ log .Printf ("[WARN] Application Auto Scaling scheduled action (%s) not found , removing from state" , d .Id ())
226245 return nil
227246 }
228247 return err
229248 }
230249
231250 return nil
232251}
252+
253+ func expandScalableTargetAction (l []interface {}) * applicationautoscaling.ScalableTargetAction {
254+ if len (l ) == 0 || l [0 ] == nil {
255+ return nil
256+ }
257+
258+ m := l [0 ].(map [string ]interface {})
259+
260+ result := & applicationautoscaling.ScalableTargetAction {}
261+
262+ if v , ok := m ["max_capacity" ]; ok {
263+ if v , null , _ := nullable .Int (v .(string )).Value (); ! null {
264+ result .MaxCapacity = aws .Int64 (v )
265+ }
266+ }
267+ if v , ok := m ["min_capacity" ]; ok {
268+ if v , null , _ := nullable .Int (v .(string )).Value (); ! null {
269+ result .MinCapacity = aws .Int64 (v )
270+ }
271+ }
272+
273+ return result
274+ }
275+
276+ func flattenScalableTargetAction (cfg * applicationautoscaling.ScalableTargetAction ) []interface {} {
277+ if cfg == nil {
278+ return []interface {}{}
279+ }
280+
281+ m := make (map [string ]interface {})
282+ if cfg .MaxCapacity != nil {
283+ m ["max_capacity" ] = strconv .FormatInt (aws .Int64Value (cfg .MaxCapacity ), 10 )
284+ }
285+ if cfg .MinCapacity != nil {
286+ m ["min_capacity" ] = strconv .FormatInt (aws .Int64Value (cfg .MinCapacity ), 10 )
287+ }
288+
289+ return []interface {}{m }
290+ }
0 commit comments