github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/provider/ec2/instance.go (about)

     1  // Copyright 2011-2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package ec2
     5  
     6  import (
     7  	stdcontext "context"
     8  	"fmt"
     9  
    10  	"github.com/aws/aws-sdk-go-v2/aws"
    11  	"github.com/aws/aws-sdk-go-v2/service/ec2"
    12  	"github.com/aws/aws-sdk-go-v2/service/ec2/types"
    13  
    14  	"github.com/juju/juju/core/instance"
    15  	"github.com/juju/juju/core/network"
    16  	"github.com/juju/juju/core/network/firewall"
    17  	"github.com/juju/juju/core/status"
    18  	"github.com/juju/juju/environs/config"
    19  	"github.com/juju/juju/environs/context"
    20  	"github.com/juju/juju/environs/instances"
    21  )
    22  
    23  // AWS SDK version of instances.Instance.
    24  type sdkInstance struct {
    25  	e *environ
    26  	i types.Instance
    27  }
    28  
    29  var _ instances.Instance = (*sdkInstance)(nil)
    30  
    31  // String returns a string representation of this instance (the ID).
    32  func (inst *sdkInstance) String() string {
    33  	return string(inst.Id())
    34  }
    35  
    36  // Id returns the EC2 identifier for the Instance.
    37  func (inst *sdkInstance) Id() instance.Id {
    38  	return instance.Id(*inst.i.InstanceId)
    39  }
    40  
    41  // AvailabilityZone returns the underlying az for an instance.
    42  func (inst *sdkInstance) AvailabilityZone() (string, bool) {
    43  	if inst.i.Placement == nil ||
    44  		inst.i.Placement.AvailabilityZone == nil {
    45  		return "", false
    46  	}
    47  	return *inst.i.Placement.AvailabilityZone, true
    48  }
    49  
    50  // Status returns the status of this EC2 instance.
    51  func (inst *sdkInstance) Status(_ context.ProviderCallContext) instance.Status {
    52  	if inst.i.State == nil || inst.i.State.Name == "" {
    53  		return instance.Status{Status: status.Empty}
    54  	}
    55  
    56  	// pending | running | shutting-down | terminated | stopping | stopped
    57  	var jujuStatus status.Status
    58  	switch inst.i.State.Name {
    59  	case "pending":
    60  		jujuStatus = status.Pending
    61  	case "running":
    62  		jujuStatus = status.Running
    63  	case "shutting-down", "terminated", "stopping", "stopped":
    64  		jujuStatus = status.Empty
    65  	default:
    66  		jujuStatus = status.Empty
    67  	}
    68  	return instance.Status{
    69  		Status:  jujuStatus,
    70  		Message: string(inst.i.State.Name),
    71  	}
    72  }
    73  
    74  // Addresses implements network.Addresses() returning generic address
    75  // details for the instance, and requerying the ec2 api if required.
    76  func (inst *sdkInstance) Addresses(_ context.ProviderCallContext) (network.ProviderAddresses, error) {
    77  	var addresses []network.ProviderAddress
    78  	if inst.i.Ipv6Address != nil {
    79  		addresses = append(addresses, network.ProviderAddress{
    80  			MachineAddress: network.MachineAddress{
    81  				Value: *inst.i.Ipv6Address,
    82  				Type:  network.IPv6Address,
    83  				Scope: network.ScopePublic,
    84  			},
    85  		})
    86  	}
    87  	if inst.i.PublicIpAddress != nil {
    88  		addresses = append(addresses, network.ProviderAddress{
    89  			MachineAddress: network.MachineAddress{
    90  				Value: *inst.i.PublicIpAddress,
    91  				Type:  network.IPv4Address,
    92  				Scope: network.ScopePublic,
    93  			},
    94  		})
    95  	}
    96  	if inst.i.PrivateIpAddress != nil {
    97  		addresses = append(addresses, network.ProviderAddress{
    98  			MachineAddress: network.MachineAddress{
    99  				Value: *inst.i.PrivateIpAddress,
   100  				Type:  network.IPv4Address,
   101  				Scope: network.ScopeCloudLocal,
   102  			},
   103  		})
   104  	}
   105  	return addresses, nil
   106  }
   107  
   108  // OpenPorts implements instances.InstanceFirewaller.
   109  func (inst *sdkInstance) OpenPorts(ctx context.ProviderCallContext, machineId string, rules firewall.IngressRules) error {
   110  	if inst.e.Config().FirewallMode() != config.FwInstance {
   111  		return fmt.Errorf("invalid firewall mode %q for opening ports on instance",
   112  			inst.e.Config().FirewallMode())
   113  	}
   114  	name := inst.e.machineGroupName(machineId)
   115  	if err := inst.e.openPortsInGroup(ctx, name, rules); err != nil {
   116  		return err
   117  	}
   118  	logger.Infof("opened ports in security group %s: %v", name, rules)
   119  	return nil
   120  }
   121  
   122  // ClosePorts implements instances.InstanceFirewaller.
   123  func (inst *sdkInstance) ClosePorts(ctx context.ProviderCallContext, machineId string, ports firewall.IngressRules) error {
   124  	if inst.e.Config().FirewallMode() != config.FwInstance {
   125  		return fmt.Errorf("invalid firewall mode %q for closing ports on instance",
   126  			inst.e.Config().FirewallMode())
   127  	}
   128  	name := inst.e.machineGroupName(machineId)
   129  	if err := inst.e.closePortsInGroup(ctx, name, ports); err != nil {
   130  		return err
   131  	}
   132  	logger.Infof("closed ports in security group %s: %v", name, ports)
   133  	return nil
   134  }
   135  
   136  // IngressRules implements instances.InstanceFirewaller.
   137  func (inst *sdkInstance) IngressRules(ctx context.ProviderCallContext, machineId string) (firewall.IngressRules, error) {
   138  	if inst.e.Config().FirewallMode() != config.FwInstance {
   139  		return nil, fmt.Errorf("invalid firewall mode %q for retrieving ingress rules from instance",
   140  			inst.e.Config().FirewallMode())
   141  	}
   142  	name := inst.e.machineGroupName(machineId)
   143  	ranges, err := inst.e.ingressRulesInGroup(ctx, name)
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  	return ranges, nil
   148  }
   149  
   150  // FetchInstanceClient describes the funcs needed from the EC2 client for
   151  // fetching instance types in a region. It's assumed that the ec2 client
   152  // conforming to this interface is scoped to the region that instances are being
   153  // requested for.
   154  type FetchInstanceClient interface {
   155  	// DescribeInstanceTypes is the same func as that of the ec2 client. See:
   156  	// https://github.com/aws/aws-sdk-go-v2/blob/service/ec2/v1.123.0/service/ec2/api_op_DescribeInstanceTypes.go#L21
   157  	DescribeInstanceTypes(stdcontext.Context, *ec2.DescribeInstanceTypesInput, ...func(*ec2.Options)) (*ec2.DescribeInstanceTypesOutput, error)
   158  }
   159  
   160  // FetchInstanceTypeInfo is responsible for fetching all of the
   161  // available instance types for an AWS region. This func assumes that the ec2
   162  // client provided is scoped to a region already.
   163  func FetchInstanceTypeInfo(
   164  	ctx context.ProviderCallContext,
   165  	ec2Client FetchInstanceClient,
   166  ) ([]types.InstanceTypeInfo, error) {
   167  	const maxResults = int32(100)
   168  
   169  	instanceTypes := []types.InstanceTypeInfo{}
   170  	var nextToken *string
   171  	for {
   172  		instTypeResults, err := ec2Client.DescribeInstanceTypes(ctx, &ec2.DescribeInstanceTypesInput{
   173  			MaxResults: aws.Int32(maxResults),
   174  			NextToken:  nextToken,
   175  		})
   176  		if err != nil {
   177  			return nil, fmt.Errorf("describing instance types: %w", err)
   178  		}
   179  		instanceTypes = append(instanceTypes, instTypeResults.InstanceTypes...)
   180  		nextToken = instTypeResults.NextToken
   181  
   182  		if nextToken == nil {
   183  			break
   184  		}
   185  	}
   186  
   187  	return instanceTypes, nil
   188  }