github.com/aldelo/common@v1.5.1/wrapper/route53/route53.go (about) 1 package route53 2 3 /* 4 * Copyright 2020-2023 Aldelo, LP 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 // ================================================================================================================= 20 // AWS CREDENTIAL: 21 // use $> aws configure (to set aws access key and secret to target machine) 22 // Store AWS Access ID and Secret Key into Default Profile Using '$ aws configure' cli 23 // 24 // To Install & Setup AWS CLI on Host: 25 // 1) https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2-linux.html 26 // On Ubuntu, if host does not have zip and unzip: 27 // $> sudo apt install zip 28 // $> sudo apt install unzip 29 // On Ubuntu, to install AWS CLI v2: 30 // $> curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" 31 // $> unzip awscliv2.zip 32 // $> sudo ./aws/install 33 // 2) $> aws configure set region awsRegionName --profile default 34 // 3) $> aws configure 35 // follow prompts to enter Access ID and Secret Key 36 // 37 // AWS Region Name Reference: 38 // us-west-2, us-east-1, ap-northeast-1, etc 39 // See: https://docs.aws.amazon.com/general/latest/gr/rande.html 40 // ================================================================================================================= 41 42 import ( 43 "context" 44 "errors" 45 awshttp2 "github.com/aldelo/common/wrapper/aws" 46 "github.com/aldelo/common/wrapper/xray" 47 "github.com/aws/aws-sdk-go/aws" 48 "github.com/aws/aws-sdk-go/aws/session" 49 "github.com/aws/aws-sdk-go/service/route53" 50 awsxray "github.com/aws/aws-xray-sdk-go/xray" 51 "net/http" 52 ) 53 54 // ================================================================================================================ 55 // STRUCTS 56 // ================================================================================================================ 57 58 // Route53 struct encapsulates the AWS Route53 partial functionality 59 type Route53 struct { 60 // custom http2 client options 61 HttpOptions *awshttp2.HttpClientSettings 62 63 // store aws session object 64 sess *session.Session 65 66 // store route 53 object 67 r53Client *route53.Route53 68 69 _parentSegment *xray.XRayParentSegment 70 } 71 72 // ================================================================================================================ 73 // STRUCTS FUNCTIONS 74 // ================================================================================================================ 75 76 // ---------------------------------------------------------------------------------------------------------------- 77 // utility functions 78 // ---------------------------------------------------------------------------------------------------------------- 79 80 // Connect will establish a connection to the Route53 service 81 func (r *Route53) Connect(parentSegment ...*xray.XRayParentSegment) (err error) { 82 if xray.XRayServiceOn() { 83 if len(parentSegment) > 0 { 84 r._parentSegment = parentSegment[0] 85 } 86 87 seg := xray.NewSegment("Route53-Connect", r._parentSegment) 88 defer seg.Close() 89 defer func() { 90 if err != nil { 91 _ = seg.Seg.AddError(err) 92 } 93 }() 94 95 err = r.connectInternal() 96 97 if err == nil { 98 awsxray.AWS(r.r53Client.Client) 99 } 100 101 return err 102 } else { 103 return r.connectInternal() 104 } 105 } 106 107 // Connect will establish a connection to the Route53 service 108 func (r *Route53) connectInternal() error { 109 // clean up prior session reference 110 r.sess = nil 111 112 // create custom http2 client if needed 113 var httpCli *http.Client 114 var httpErr error 115 116 if r.HttpOptions == nil { 117 r.HttpOptions = new(awshttp2.HttpClientSettings) 118 } 119 120 // use custom http2 client 121 h2 := &awshttp2.AwsHttp2Client{ 122 Options: r.HttpOptions, 123 } 124 125 if httpCli, httpErr = h2.NewHttp2Client(); httpErr != nil { 126 return errors.New("Connect to Route53 Failed: (AWS Session Error) " + "Create Custom Http2 Client Errored = " + httpErr.Error()) 127 } 128 129 // establish aws session connection and keep session object in struct 130 if sess, err := session.NewSession( 131 &aws.Config{ 132 HTTPClient: httpCli, 133 }); err != nil { 134 // aws session error 135 return errors.New("Connect To Route53 Failed: (AWS Session Error) " + err.Error()) 136 } else { 137 // aws session obtained 138 r.sess = sess 139 140 // create cached objects for shared use 141 r.r53Client = route53.New(r.sess) 142 143 if r.r53Client == nil { 144 return errors.New("Connect To Route53 Client Failed: (New Route53 Client Connection) " + "Connection Object Nil") 145 } 146 147 // session stored to struct 148 return nil 149 } 150 } 151 152 // Disconnect will disjoin from aws session by clearing it 153 func (r *Route53) Disconnect() { 154 r.r53Client = nil 155 r.sess = nil 156 } 157 158 // UpdateParentSegment updates this struct's xray parent segment, if no parent segment, set nil 159 func (r *Route53) UpdateParentSegment(parentSegment *xray.XRayParentSegment) { 160 r._parentSegment = parentSegment 161 } 162 163 // ---------------------------------------------------------------------------------------------------------------- 164 // basic resource record set functions 165 // ---------------------------------------------------------------------------------------------------------------- 166 167 // CreateUpdateResourceRecordset will create or update a dns recordset to route53 168 // 169 // hostedZoneID = root domain hosted zone id (from aws route 53) 170 // url = fully qualified domain name url, such as abc.example.com 171 // ip = recordset IPv4 address 172 // ttl = 15 - 300 (defaults to 60 if ttl is 0) 173 // recordType = A (currently only A is supported via this function) 174 func (r *Route53) CreateUpdateResourceRecordset(hostedZoneID string, url string, ip string, ttl uint, recordType string) (err error) { 175 var segCtx context.Context 176 segCtx = nil 177 178 seg := xray.NewSegmentNullable("Route53-CreateUpdateResourceRecordset", r._parentSegment) 179 180 if seg != nil { 181 segCtx = seg.Ctx 182 183 defer seg.Close() 184 defer func() { 185 _ = seg.Seg.AddMetadata("Route53-HostedZoneID", hostedZoneID) 186 _ = seg.Seg.AddMetadata("Route53-URL", url) 187 _ = seg.Seg.AddMetadata("Route53-IP", ip) 188 _ = seg.Seg.AddMetadata("Route53-TTL", ttl) 189 _ = seg.Seg.AddMetadata("Route53-RecordType", recordType) 190 191 if err != nil { 192 _ = seg.Seg.AddError(err) 193 } 194 }() 195 } 196 197 // validate 198 if r.r53Client == nil { 199 err = errors.New("CreateUpdateResourceRecordset Failed: " + "Route53 Client is Required") 200 return err 201 } 202 203 if len(hostedZoneID) <= 0 { 204 err = errors.New("CreateUpdateResourceRecordset Failed: " + "Hosted Zone ID is Required") 205 return err 206 } 207 208 if len(url) <= 0 { 209 err = errors.New("CreateUpdateResourceRecordset Failed: " + "URL is Required") 210 return err 211 } 212 213 if len(ip) <= 0 { 214 err = errors.New("CreateUpdateResourceRecordset Failed: " + "IP is Required") 215 return err 216 } 217 218 if ttl == 0 { 219 ttl = 60 220 } else if ttl < 15 { 221 ttl = 15 222 } else if ttl > 300 { 223 ttl = 300 224 } 225 226 if recordType != "A" { 227 recordType = "A" 228 } 229 230 // create 231 input := &route53.ChangeResourceRecordSetsInput{ 232 ChangeBatch: &route53.ChangeBatch{ 233 Changes: []*route53.Change{ 234 { 235 Action: aws.String("UPSERT"), 236 ResourceRecordSet: &route53.ResourceRecordSet{ 237 Name: aws.String(url), 238 ResourceRecords: []*route53.ResourceRecord{ 239 { 240 Value: aws.String(ip), 241 }, 242 }, 243 TTL: aws.Int64(int64(ttl)), 244 Type: aws.String(recordType), 245 }, 246 }, 247 }, 248 }, 249 HostedZoneId: aws.String(hostedZoneID), 250 } 251 252 if segCtx == nil { 253 _, err = r.r53Client.ChangeResourceRecordSets(input) 254 } else { 255 _, err = r.r53Client.ChangeResourceRecordSetsWithContext(segCtx, input) 256 } 257 258 if err != nil { 259 err = errors.New("CreateUpdateResourceRecordset Failed: " + err.Error()) 260 return err 261 } else { 262 return nil 263 } 264 } 265 266 // DeleteResourceRecordset will delete a dns recordset from route53 267 // 268 // hostedZoneID = root domain hosted zone id (from aws route 53) 269 // url = fully qualified domain name url, such as abc.example.com 270 // ip = recordset IPv4 address 271 // ttl = 15 - 300 (defaults to 60 if ttl is 0) 272 // recordType = A (currently only A is supported via this function) 273 func (r *Route53) DeleteResourceRecordset(hostedZoneID string, url string, ip string, ttl uint, recordType string) (err error) { 274 var segCtx context.Context 275 segCtx = nil 276 277 seg := xray.NewSegmentNullable("Route53-DeleteResourceRecordset", r._parentSegment) 278 279 if seg != nil { 280 segCtx = seg.Ctx 281 282 defer seg.Close() 283 defer func() { 284 _ = seg.Seg.AddMetadata("Route53-HostedZoneID", hostedZoneID) 285 _ = seg.Seg.AddMetadata("Route53-URL", url) 286 _ = seg.Seg.AddMetadata("Route53-IP", ip) 287 _ = seg.Seg.AddMetadata("Route53-TTL", ttl) 288 _ = seg.Seg.AddMetadata("Route53-RecordType", recordType) 289 290 if err != nil { 291 _ = seg.Seg.AddError(err) 292 } 293 }() 294 } 295 296 // validate 297 if r.r53Client == nil { 298 err = errors.New("CreateUpdateResourceRecordset Failed: " + "Route53 Client is Required") 299 return err 300 } 301 302 if len(hostedZoneID) <= 0 { 303 err = errors.New("CreateUpdateResourceRecordset Failed: " + "Hosted Zone ID is Required") 304 return err 305 } 306 307 if len(url) <= 0 { 308 err = errors.New("CreateUpdateResourceRecordset Failed: " + "URL is Required") 309 return err 310 } 311 312 if len(ip) <= 0 { 313 err = errors.New("CreateUpdateResourceRecordset Failed: " + "IP is Required") 314 return err 315 } 316 317 if ttl == 0 { 318 ttl = 60 319 } else if ttl < 15 { 320 ttl = 15 321 } else if ttl > 300 { 322 ttl = 300 323 } 324 325 if recordType != "A" { 326 recordType = "A" 327 } 328 329 // create 330 input := &route53.ChangeResourceRecordSetsInput{ 331 ChangeBatch: &route53.ChangeBatch{ 332 Changes: []*route53.Change{ 333 { 334 Action: aws.String("DELETE"), 335 ResourceRecordSet: &route53.ResourceRecordSet{ 336 Name: aws.String(url), 337 ResourceRecords: []*route53.ResourceRecord{ 338 { 339 Value: aws.String(ip), 340 }, 341 }, 342 TTL: aws.Int64(int64(ttl)), 343 Type: aws.String(recordType), 344 }, 345 }, 346 }, 347 }, 348 HostedZoneId: aws.String(hostedZoneID), 349 } 350 351 if segCtx == nil { 352 _, err = r.r53Client.ChangeResourceRecordSets(input) 353 } else { 354 _, err = r.r53Client.ChangeResourceRecordSetsWithContext(segCtx, input) 355 } 356 357 if err != nil { 358 err = errors.New("DeleteResourceRecordset Failed: " + err.Error()) 359 return err 360 } else { 361 return nil 362 } 363 }