1/* Copyright 2017 WALLIX
2
3Licensed under the Apache License, Version 2.0 (the "License");
4you may not use this file except in compliance with the License.
5You may obtain a copy of the License at
6
7    http://www.apache.org/licenses/LICENSE-2.0
8
9Unless required by applicable law or agreed to in writing, software
10distributed under the License is distributed on an "AS IS" BASIS,
11WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12See the License for the specific language governing permissions and
13limitations under the License.
14*/
15
16package awsspec
17
18import (
19	"errors"
20	"fmt"
21
22	"github.com/wallix/awless/cloud"
23	"github.com/wallix/awless/template/env"
24	"github.com/wallix/awless/template/params"
25
26	"github.com/aws/aws-sdk-go/aws"
27	"github.com/aws/aws-sdk-go/service/cloudwatch"
28	"github.com/aws/aws-sdk-go/service/cloudwatch/cloudwatchiface"
29	"github.com/wallix/awless/logger"
30)
31
32type CreateAlarm struct {
33	_                       string `action:"create" entity:"alarm" awsAPI:"cloudwatch" awsCall:"PutMetricAlarm" awsInput:"cloudwatch.PutMetricAlarmInput" awsOutput:"cloudwatch.PutMetricAlarmOutput"`
34	logger                  *logger.Logger
35	graph                   cloud.GraphAPI
36	api                     cloudwatchiface.CloudWatchAPI
37	Name                    *string   `awsName:"AlarmName" awsType:"awsstr" templateName:"name"`
38	Operator                *string   `awsName:"ComparisonOperator" awsType:"awsstr" templateName:"operator"`
39	Metric                  *string   `awsName:"MetricName" awsType:"awsstr" templateName:"metric"`
40	Namespace               *string   `awsName:"Namespace" awsType:"awsstr" templateName:"namespace"`
41	EvaluationPeriods       *int64    `awsName:"EvaluationPeriods" awsType:"awsint64" templateName:"evaluation-periods"`
42	Period                  *int64    `awsName:"Period" awsType:"awsint64" templateName:"period"`
43	StatisticFunction       *string   `awsName:"Statistic" awsType:"awsstr" templateName:"statistic-function"`
44	Threshold               *float64  `awsName:"Threshold" awsType:"awsfloat" templateName:"threshold"`
45	Enabled                 *bool     `awsName:"ActionsEnabled" awsType:"awsbool" templateName:"enabled"`
46	AlarmActions            []*string `awsName:"AlarmActions" awsType:"awsstringslice" templateName:"alarm-actions"`
47	InsufficientdataActions []*string `awsName:"InsufficientDataActions" awsType:"awsstringslice" templateName:"insufficientdata-actions"`
48	OkActions               []*string `awsName:"OKActions" awsType:"awsstringslice" templateName:"ok-actions"`
49	Description             *string   `awsName:"AlarmDescription" awsType:"awsstr" templateName:"description"`
50	Dimensions              []*string `awsName:"Dimensions" awsType:"awsdimensionslice" templateName:"dimensions"`
51	Unit                    *string   `awsName:"Unit" awsType:"awsstr" templateName:"unit"`
52}
53
54func (cmd *CreateAlarm) ParamsSpec() params.Spec {
55	return params.NewSpec(
56		params.AllOf(params.Key("evaluation-periods"), params.Key("metric"), params.Key("name"), params.Key("namespace"), params.Key("operator"), params.Key("period"), params.Key("statistic-function"), params.Key("threshold"),
57			params.Opt("alarm-actions", "description", "dimensions", "enabled", "insufficientdata-actions", "ok-actions", "unit"),
58		),
59		params.Validators{
60			"operator": params.IsInEnumIgnoreCase("GreaterThanThreshold", "LessThanThreshold", "LessThanOrEqualToThreshold", "GreaterThanOrEqualToThreshold"),
61		})
62}
63
64func (cmd *CreateAlarm) ExtractResult(i interface{}) string {
65	return StringValue(cmd.Name)
66}
67
68type DeleteAlarm struct {
69	_      string `action:"delete" entity:"alarm" awsAPI:"cloudwatch" awsCall:"DeleteAlarms" awsInput:"cloudwatch.DeleteAlarmsInput" awsOutput:"cloudwatch.DeleteAlarmsOutput"`
70	logger *logger.Logger
71	graph  cloud.GraphAPI
72	api    cloudwatchiface.CloudWatchAPI
73	Name   []*string `awsName:"AlarmNames" awsType:"awsstringslice" templateName:"name"`
74}
75
76func (cmd *DeleteAlarm) ParamsSpec() params.Spec {
77	return params.NewSpec(params.AllOf(params.Key("name")))
78}
79
80type StartAlarm struct {
81	_      string `action:"start" entity:"alarm" awsAPI:"cloudwatch" awsCall:"EnableAlarmActions" awsInput:"cloudwatch.EnableAlarmActionsInput" awsOutput:"cloudwatch.EnableAlarmActionsOutput"`
82	logger *logger.Logger
83	graph  cloud.GraphAPI
84	api    cloudwatchiface.CloudWatchAPI
85	Names  []*string `awsName:"AlarmNames" awsType:"awsstringslice" templateName:"names"`
86}
87
88func (cmd *StartAlarm) ParamsSpec() params.Spec {
89	return params.NewSpec(params.AllOf(params.Key("names")))
90}
91
92type StopAlarm struct {
93	_      string `action:"stop" entity:"alarm" awsAPI:"cloudwatch" awsCall:"DisableAlarmActions" awsInput:"cloudwatch.DisableAlarmActionsInput" awsOutput:"cloudwatch.DisableAlarmActionsOutput"`
94	logger *logger.Logger
95	graph  cloud.GraphAPI
96	api    cloudwatchiface.CloudWatchAPI
97	Names  []*string `awsName:"AlarmNames" awsType:"awsstringslice" templateName:"names"`
98}
99
100func (cmd *StopAlarm) ParamsSpec() params.Spec {
101	return params.NewSpec(params.AllOf(params.Key("names")))
102}
103
104type AttachAlarm struct {
105	_         string `action:"attach" entity:"alarm" awsAPI:"cloudwatch"`
106	logger    *logger.Logger
107	graph     cloud.GraphAPI
108	api       cloudwatchiface.CloudWatchAPI
109	Name      *string `templateName:"name"`
110	ActionArn *string `templateName:"action-arn"`
111}
112
113func (cmd *AttachAlarm) ParamsSpec() params.Spec {
114	return params.NewSpec(params.AllOf(params.Key("action-arn"), params.Key("name")))
115}
116
117func (cmd *AttachAlarm) ManualRun(renv env.Running) (interface{}, error) {
118	alarm, err := getAlarm(cmd.api, cmd.Name)
119	if err != nil {
120		return nil, err
121	}
122	alarm.AlarmActions = append(alarm.AlarmActions, cmd.ActionArn)
123
124	return cmd.api.PutMetricAlarm(&cloudwatch.PutMetricAlarmInput{
125		ActionsEnabled:                   alarm.ActionsEnabled,
126		AlarmActions:                     alarm.AlarmActions,
127		AlarmDescription:                 alarm.AlarmDescription,
128		AlarmName:                        alarm.AlarmName,
129		ComparisonOperator:               alarm.ComparisonOperator,
130		Dimensions:                       alarm.Dimensions,
131		EvaluateLowSampleCountPercentile: alarm.EvaluateLowSampleCountPercentile,
132		EvaluationPeriods:                alarm.EvaluationPeriods,
133		ExtendedStatistic:                alarm.ExtendedStatistic,
134		InsufficientDataActions:          alarm.InsufficientDataActions,
135		MetricName:                       alarm.MetricName,
136		Namespace:                        alarm.Namespace,
137		OKActions:                        alarm.OKActions,
138		Period:                           alarm.Period,
139		Statistic:                        alarm.Statistic,
140		Threshold:                        alarm.Threshold,
141		TreatMissingData:                 alarm.TreatMissingData,
142		Unit:                             alarm.Unit,
143	})
144}
145
146type DetachAlarm struct {
147	_         string `action:"detach" entity:"alarm" awsAPI:"cloudwatch"`
148	logger    *logger.Logger
149	graph     cloud.GraphAPI
150	api       cloudwatchiface.CloudWatchAPI
151	Name      *string `templateName:"name"`
152	ActionArn *string `templateName:"action-arn"`
153}
154
155func (cmd *DetachAlarm) ParamsSpec() params.Spec {
156	return params.NewSpec(params.AllOf(params.Key("action-arn"), params.Key("name")))
157}
158
159func (cmd *DetachAlarm) ManualRun(renv env.Running) (interface{}, error) {
160	alarm, err := getAlarm(cmd.api, cmd.Name)
161	if err != nil {
162		return nil, err
163	}
164	actionArn := aws.StringValue(cmd.ActionArn)
165	var found bool
166	var updatedActions []*string
167	for _, action := range alarm.AlarmActions {
168		if aws.StringValue(action) == actionArn {
169			found = true
170		} else {
171			updatedActions = append(updatedActions, action)
172		}
173	}
174	if !found {
175		return nil, fmt.Errorf("detach alarm: action '%s' is not attached to alarm actions of alarm %s", actionArn, aws.StringValue(alarm.AlarmName))
176	}
177
178	return cmd.api.PutMetricAlarm(&cloudwatch.PutMetricAlarmInput{
179		ActionsEnabled:                   alarm.ActionsEnabled,
180		AlarmActions:                     updatedActions,
181		AlarmDescription:                 alarm.AlarmDescription,
182		AlarmName:                        alarm.AlarmName,
183		ComparisonOperator:               alarm.ComparisonOperator,
184		Dimensions:                       alarm.Dimensions,
185		EvaluateLowSampleCountPercentile: alarm.EvaluateLowSampleCountPercentile,
186		EvaluationPeriods:                alarm.EvaluationPeriods,
187		ExtendedStatistic:                alarm.ExtendedStatistic,
188		InsufficientDataActions:          alarm.InsufficientDataActions,
189		MetricName:                       alarm.MetricName,
190		Namespace:                        alarm.Namespace,
191		OKActions:                        alarm.OKActions,
192		Period:                           alarm.Period,
193		Statistic:                        alarm.Statistic,
194		Threshold:                        alarm.Threshold,
195		TreatMissingData:                 alarm.TreatMissingData,
196		Unit:                             alarm.Unit,
197	})
198}
199
200func getAlarm(api cloudwatchiface.CloudWatchAPI, name *string) (*cloudwatch.MetricAlarm, error) {
201	if name == nil {
202		return nil, errors.New("missing required params 'name'")
203	}
204	out, err := api.DescribeAlarms(&cloudwatch.DescribeAlarmsInput{AlarmNames: []*string{name}})
205	if err != nil {
206		return nil, err
207	}
208	if l := len(out.MetricAlarms); l == 0 {
209		return nil, fmt.Errorf("alarm '%s' not found", StringValue(name))
210	} else if l > 1 {
211		return nil, fmt.Errorf("%d alarms found with name '%s'", l, StringValue(name))
212	}
213	return out.MetricAlarms[0], nil
214}
215