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