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	"fmt"
20	"time"
21
22	"github.com/wallix/awless/cloud"
23	"github.com/wallix/awless/template/env"
24	"github.com/wallix/awless/template/params"
25
26	awssdk "github.com/aws/aws-sdk-go/aws"
27	"github.com/aws/aws-sdk-go/aws/awserr"
28	"github.com/aws/aws-sdk-go/service/rds"
29	"github.com/aws/aws-sdk-go/service/rds/rdsiface"
30	"github.com/wallix/awless/logger"
31)
32
33type CreateDatabase struct {
34	_      string `action:"create" entity:"database" awsAPI:"rds"`
35	logger *logger.Logger
36	graph  cloud.GraphAPI
37	api    rdsiface.RDSAPI
38
39	// Required for DB
40	Type     *string `awsName:"DBInstanceClass" awsType:"awsstr" templateName:"type"`
41	Id       *string `awsName:"DBInstanceIdentifier" awsType:"awsstr" templateName:"id"`
42	Engine   *string `awsName:"Engine" awsType:"awsstr" templateName:"engine"`
43	Password *string `awsName:"MasterUserPassword" awsType:"awsstr" templateName:"password"`
44	Username *string `awsName:"MasterUsername" awsType:"awsstr" templateName:"username"`
45	Size     *int64  `awsName:"AllocatedStorage" awsType:"awsint64" templateName:"size"`
46
47	// Required for read replica DB
48	ReadReplicaSourceDB   *string `awsName:"SourceDBInstanceIdentifier" awsType:"awsstr" templateName:"replica-source"`
49	ReadReplicaIdentifier *string `awsName:"DBInstanceIdentifier" awsType:"awsstr" templateName:"replica"`
50
51	// Extras common to both replica DB and source DB
52	Autoupgrade      *bool   `awsName:"AutoMinorVersionUpgrade" awsType:"awsbool" templateName:"autoupgrade"`
53	Availabilityzone *string `awsName:"AvailabilityZone" awsType:"awsstr" templateName:"availabilityzone"`
54	Subnetgroup      *string `awsName:"DBSubnetGroupName" awsType:"awsstr" templateName:"subnetgroup"`
55	Iops             *int64  `awsName:"Iops" awsType:"awsint64" templateName:"iops"`
56	Optiongroup      *string `awsName:"OptionGroupName" awsType:"awsstr" templateName:"optiongroup"`
57	Port             *int64  `awsName:"Port" awsType:"awsint64" templateName:"port"`
58	Public           *bool   `awsName:"PubliclyAccessible" awsType:"awsbool" templateName:"public"`
59	Storagetype      *string `awsName:"StorageType" awsType:"awsstr" templateName:"storagetype"`
60
61	// Extra only for DB
62	Backupretention   *int64    `awsName:"BackupRetentionPeriod" awsType:"awsint64" templateName:"backupretention"`
63	Backupwindow      *string   `awsName:"PreferredBackupWindow" awsType:"awsstr" templateName:"backupwindow"`
64	Cluster           *string   `awsName:"DBClusterIdentifier" awsType:"awsstr" templateName:"cluster"`
65	Dbname            *string   `awsName:"DBName" awsType:"awsstr" templateName:"dbname"`
66	Dbsecuritygroups  []*string `awsName:"DBSecurityGroups" awsType:"awsstringslice" templateName:"dbsecuritygroups"`
67	Domain            *string   `awsName:"Domain" awsType:"awsstr" templateName:"domain"`
68	Encrypted         *bool     `awsName:"StorageEncrypted" awsType:"awsbool" templateName:"encrypted"`
69	Iamrole           *string   `awsName:"DomainIAMRoleName" awsType:"awsstr" templateName:"iamrole"`
70	License           *string   `awsName:"LicenseModel" awsType:"awsstr" templateName:"license"`
71	Maintenancewindow *string   `awsName:"PreferredMaintenanceWindow" awsType:"awsstr" templateName:"maintenancewindow"`
72	Multiaz           *bool     `awsName:"MultiAZ" awsType:"awsbool" templateName:"multiaz"`
73	Parametergroup    *string   `awsName:"DBParameterGroupName" awsType:"awsstr" templateName:"parametergroup"`
74	Timezone          *string   `awsName:"Timezone" awsType:"awsstr" templateName:"timezone"`
75	Vpcsecuritygroups []*string `awsName:"VpcSecurityGroupIds" awsType:"awsstringslice" templateName:"vpcsecuritygroups"`
76	Version           *string   `awsName:"EngineVersion" awsType:"awsstr" templateName:"version"`
77
78	// Extra only for replica DB
79	CopyTagsToSnapshot *string `awsName:"CopyTagsToSnapshot" awsType:"awsbool" templateName:"copytagstosnapshot"`
80}
81
82func (cmd *CreateDatabase) ParamsSpec() params.Spec {
83	return params.NewSpec(params.OnlyOneOf(
84		params.AllOf(params.Key("type"), params.Key("id"), params.Key("engine"), params.Key("password"), params.Key("username"), params.Key("size")),
85		params.AllOf(params.Key("replica"), params.Key("replica-source")),
86		params.Opt("autoupgrade", "availabilityzone", "backupretention", "cluster", "dbname", "parametergroup",
87			"dbsecuritygroups", "subnetgroup", "domain", "iamrole", "version", "iops", "license", "multiaz", "optiongroup",
88			"port", "backupwindow", "maintenancewindow", "public", "encrypted", "storagetype", "timezone", "vpcsecuritygroups")),
89		params.Validators{
90			"password": params.MinLengthOf(8),
91			"replica": func(i interface{}, others map[string]interface{}) error {
92				msg := "param not allowed in replica (either not applicable or directly inherited from the source DB)"
93				if _, ok := others["backupretention"]; ok {
94					return fmt.Errorf("'backupretention' %s", msg)
95				}
96				if _, ok := others["backupwindow"]; ok {
97					return fmt.Errorf("'backupwindow' %s", msg)
98				}
99				if _, ok := others["cluster"]; ok {
100					return fmt.Errorf("'cluster' %s", msg)
101				}
102				if _, ok := others["dbname"]; ok {
103					return fmt.Errorf("'dbname' %s", msg)
104				}
105				if _, ok := others["dbsecuritygroups"]; ok {
106					return fmt.Errorf("'dbsecuritygroups' %s", msg)
107				}
108				if _, ok := others["domain"]; ok {
109					return fmt.Errorf("'domain' %s", msg)
110				}
111				if _, ok := others["encrypted"]; ok {
112					return fmt.Errorf("'encrypted' %s", msg)
113				}
114				if _, ok := others["iamrole"]; ok {
115					return fmt.Errorf("'iamrole' %s", msg)
116				}
117				if _, ok := others["license"]; ok {
118					return fmt.Errorf("'license' %s", msg)
119				}
120				if _, ok := others["maintenancewindow"]; ok {
121					return fmt.Errorf("'maintenancewindow' %s", msg)
122				}
123				if _, ok := others["multiaz"]; ok {
124					return fmt.Errorf("'multiaz' %s", msg)
125				}
126				if _, ok := others["parametergroup"]; ok {
127					return fmt.Errorf("'parametergroup' %s", msg)
128				}
129				if _, ok := others["timezone"]; ok {
130					return fmt.Errorf("'timezone' %s", msg)
131				}
132				if _, ok := others["vpcsecuritygroups"]; ok {
133					return fmt.Errorf("'vpcsecuritygroups' %s", msg)
134				}
135				if _, ok := others["version"]; ok {
136					return fmt.Errorf("'version' %s", msg)
137				}
138				return nil
139			},
140		},
141	)
142}
143
144func (cmd *CreateDatabase) ManualRun(renv env.Running) (output interface{}, err error) {
145	if replica := cmd.ReadReplicaIdentifier; replica != nil {
146		input := &rds.CreateDBInstanceReadReplicaInput{}
147		if ierr := structInjector(cmd, input, renv.Context()); ierr != nil {
148			return nil, fmt.Errorf("cannot inject in rds.CreateDBInstanceReadReplicaInput: %s", ierr)
149		}
150		start := time.Now()
151		output, err = cmd.api.CreateDBInstanceReadReplica(input)
152		cmd.logger.ExtraVerbosef("rds.CreateDBInstanceReadReplica call took %s", time.Since(start))
153	} else {
154		input := &rds.CreateDBInstanceInput{}
155		if ierr := structInjector(cmd, input, renv.Context()); ierr != nil {
156			return nil, fmt.Errorf("cannot inject in rds.CreateDBInstanceInput: %s", ierr)
157		}
158		start := time.Now()
159		output, err = cmd.api.CreateDBInstance(input)
160		cmd.logger.ExtraVerbosef("rds.CreateDBInstance call took %s", time.Since(start))
161	}
162	if err != nil {
163		return output, err
164	}
165	return output, nil
166}
167
168func (cmd *CreateDatabase) ExtractResult(i interface{}) string {
169	switch i.(type) {
170	case *rds.CreateDBInstanceOutput:
171		return awssdk.StringValue(i.(*rds.CreateDBInstanceOutput).DBInstance.DBInstanceIdentifier)
172	case *rds.CreateDBInstanceReadReplicaOutput:
173		return awssdk.StringValue(i.(*rds.CreateDBInstanceReadReplicaOutput).DBInstance.DBInstanceIdentifier)
174	default:
175		logger.Errorf("unexpected interface type %T", i)
176		return ""
177	}
178}
179
180type DeleteDatabase struct {
181	_            string `action:"delete" entity:"database" awsAPI:"rds" awsCall:"DeleteDBInstance" awsInput:"rds.DeleteDBInstanceInput" awsOutput:"rds.DeleteDBInstanceOutput"`
182	logger       *logger.Logger
183	graph        cloud.GraphAPI
184	api          rdsiface.RDSAPI
185	Id           *string `awsName:"DBInstanceIdentifier" awsType:"awsstr" templateName:"id"`
186	SkipSnapshot *bool   `awsName:"SkipFinalSnapshot" awsType:"awsbool" templateName:"skip-snapshot"`
187	Snapshot     *string `awsName:"FinalDBSnapshotIdentifier" awsType:"awsstr" templateName:"snapshot"`
188}
189
190func (cmd *DeleteDatabase) ParamsSpec() params.Spec {
191	return params.NewSpec(params.AllOf(params.Key("id"),
192		params.Opt("skip-snapshot", "snapshot"),
193	))
194}
195
196type CheckDatabase struct {
197	_       string `action:"check" entity:"database" awsAPI:"rds"`
198	logger  *logger.Logger
199	graph   cloud.GraphAPI
200	api     rdsiface.RDSAPI
201	Id      *string `templateName:"id"`
202	State   *string `templateName:"state"`
203	Timeout *int64  `templateName:"timeout"`
204}
205
206func (cmd *CheckDatabase) ParamsSpec() params.Spec {
207	return params.NewSpec(
208		params.AllOf(params.Key("id"), params.Key("state"), params.Key("timeout")),
209		params.Validators{
210			"state": params.IsInEnumIgnoreCase("available",
211				"backing-up", "creating", "deleting", "failed", "maintenance", "modifying",
212				"rebooting", "renaming", "resetting-master-credentials", "restore-error",
213				"storage-full", "upgrading", notFoundState),
214		},
215	)
216}
217
218func (cmd *CheckDatabase) ManualRun(renv env.Running) (interface{}, error) {
219	input := &rds.DescribeDBInstancesInput{
220		DBInstanceIdentifier: cmd.Id,
221	}
222
223	c := &checker{
224		description: fmt.Sprintf("database %s", StringValue(cmd.Id)),
225		timeout:     time.Duration(Int64AsIntValue(cmd.Timeout)) * time.Second,
226		frequency:   5 * time.Second,
227		fetchFunc: func() (string, error) {
228			output, err := cmd.api.DescribeDBInstances(input)
229			if err != nil {
230				if awserr, ok := err.(awserr.Error); ok {
231					if awserr.Code() == "DatabaseNotFound" {
232						return notFoundState, nil
233					}
234				} else {
235					return "", err
236				}
237			} else {
238				if res := output.DBInstances; len(res) > 0 {
239					for _, dbinst := range res {
240						if StringValue(dbinst.DBInstanceIdentifier) == StringValue(cmd.Id) {
241							return StringValue(dbinst.DBInstanceStatus), nil
242						}
243					}
244				}
245			}
246			return notFoundState, nil
247		},
248		expect: StringValue(cmd.State),
249		logger: cmd.logger,
250	}
251	return nil, c.check()
252}
253
254type StartDatabase struct {
255	_      string `action:"start" entity:"database" awsAPI:"rds" awsCall:"StartDBInstance" awsInput:"rds.StartDBInstanceInput" awsOutput:"rds.StartDBInstanceOutput"`
256	logger *logger.Logger
257	graph  cloud.GraphAPI
258	api    rdsiface.RDSAPI
259	Id     *string `awsName:"DBInstanceIdentifier" awsType:"awsstr" templateName:"id"`
260}
261
262func (cmd *StartDatabase) ParamsSpec() params.Spec {
263	return params.NewSpec(params.AllOf(params.Key("id")))
264}
265
266type StopDatabase struct {
267	_      string `action:"stop" entity:"database" awsAPI:"rds" awsCall:"StopDBInstance" awsInput:"rds.StopDBInstanceInput" awsOutput:"rds.StopDBInstanceOutput"`
268	logger *logger.Logger
269	graph  cloud.GraphAPI
270	api    rdsiface.RDSAPI
271	Id     *string `awsName:"DBInstanceIdentifier" awsType:"awsstr" templateName:"id"`
272}
273
274func (cmd *StopDatabase) ParamsSpec() params.Spec {
275	return params.NewSpec(params.AllOf(params.Key("id")))
276}
277
278type RestartDatabase struct {
279	_            string `action:"restart" entity:"database" awsAPI:"rds" awsCall:"RebootDBInstance" awsInput:"rds.RebootDBInstanceInput" awsOutput:"rds.RebootDBInstanceOutput"`
280	logger       *logger.Logger
281	graph        cloud.GraphAPI
282	api          rdsiface.RDSAPI
283	Id           *string `awsName:"DBInstanceIdentifier" awsType:"awsstr" templateName:"id"`
284	WithFailover *bool   `awsName:"ForceFailover" awsType:"awsbool" templateName:"with-failover"`
285}
286
287func (cmd *RestartDatabase) ParamsSpec() params.Spec {
288	return params.NewSpec(params.AllOf(params.Key("id"), params.Opt("with-failover")))
289}
290