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 }