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  }