sdk.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. package amazon
  2. import (
  3. "context"
  4. "fmt"
  5. "time"
  6. "github.com/aws/aws-sdk-go/aws"
  7. "github.com/aws/aws-sdk-go/aws/session"
  8. "github.com/aws/aws-sdk-go/service/cloudformation"
  9. "github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface"
  10. "github.com/aws/aws-sdk-go/service/cloudwatchlogs"
  11. "github.com/aws/aws-sdk-go/service/cloudwatchlogs/cloudwatchlogsiface"
  12. "github.com/aws/aws-sdk-go/service/ec2"
  13. "github.com/aws/aws-sdk-go/service/ec2/ec2iface"
  14. "github.com/aws/aws-sdk-go/service/ecs"
  15. "github.com/aws/aws-sdk-go/service/ecs/ecsiface"
  16. "github.com/aws/aws-sdk-go/service/elbv2"
  17. "github.com/aws/aws-sdk-go/service/elbv2/elbv2iface"
  18. "github.com/aws/aws-sdk-go/service/iam"
  19. "github.com/aws/aws-sdk-go/service/iam/iamiface"
  20. "github.com/aws/aws-sdk-go/service/secretsmanager"
  21. "github.com/aws/aws-sdk-go/service/secretsmanager/secretsmanageriface"
  22. cf "github.com/awslabs/goformation/v4/cloudformation"
  23. "github.com/sirupsen/logrus"
  24. "github.com/docker/ecs-plugin/pkg/docker"
  25. )
  26. type sdk struct {
  27. sess *session.Session
  28. ECS ecsiface.ECSAPI
  29. EC2 ec2iface.EC2API
  30. ELB elbv2iface.ELBV2API
  31. CW cloudwatchlogsiface.CloudWatchLogsAPI
  32. IAM iamiface.IAMAPI
  33. CF cloudformationiface.CloudFormationAPI
  34. SM secretsmanageriface.SecretsManagerAPI
  35. }
  36. func NewAPI(sess *session.Session) API {
  37. return sdk{
  38. ECS: ecs.New(sess),
  39. EC2: ec2.New(sess),
  40. ELB: elbv2.New(sess),
  41. CW: cloudwatchlogs.New(sess),
  42. IAM: iam.New(sess),
  43. CF: cloudformation.New(sess),
  44. SM: secretsmanager.New(sess),
  45. }
  46. }
  47. func (s sdk) ClusterExists(ctx context.Context, name string) (bool, error) {
  48. logrus.Debug("Check if cluster was already created: ", name)
  49. clusters, err := s.ECS.DescribeClustersWithContext(ctx, &ecs.DescribeClustersInput{
  50. Clusters: []*string{aws.String(name)},
  51. })
  52. if err != nil {
  53. return false, err
  54. }
  55. return len(clusters.Clusters) > 0, nil
  56. }
  57. func (s sdk) CreateCluster(ctx context.Context, name string) (string, error) {
  58. logrus.Debug("Create cluster ", name)
  59. response, err := s.ECS.CreateClusterWithContext(ctx, &ecs.CreateClusterInput{ClusterName: aws.String(name)})
  60. if err != nil {
  61. return "", err
  62. }
  63. return *response.Cluster.Status, nil
  64. }
  65. func (s sdk) DeleteCluster(ctx context.Context, name string) error {
  66. logrus.Debug("Delete cluster ", name)
  67. response, err := s.ECS.DeleteClusterWithContext(ctx, &ecs.DeleteClusterInput{Cluster: aws.String(name)})
  68. if err != nil {
  69. return err
  70. }
  71. if *response.Cluster.Status == "INACTIVE" {
  72. return nil
  73. }
  74. return fmt.Errorf("Failed to delete cluster, status: %s" + *response.Cluster.Status)
  75. }
  76. func (s sdk) VpcExists(ctx context.Context, vpcID string) (bool, error) {
  77. logrus.Debug("Check if VPC exists: ", vpcID)
  78. _, err := s.EC2.DescribeVpcsWithContext(ctx, &ec2.DescribeVpcsInput{VpcIds: []*string{&vpcID}})
  79. return err == nil, err
  80. }
  81. func (s sdk) GetDefaultVPC(ctx context.Context) (string, error) {
  82. logrus.Debug("Retrieve default VPC")
  83. vpcs, err := s.EC2.DescribeVpcsWithContext(ctx, &ec2.DescribeVpcsInput{
  84. Filters: []*ec2.Filter{
  85. {
  86. Name: aws.String("isDefault"),
  87. Values: []*string{aws.String("true")},
  88. },
  89. },
  90. })
  91. if err != nil {
  92. return "", err
  93. }
  94. if len(vpcs.Vpcs) == 0 {
  95. return "", fmt.Errorf("account has not default VPC")
  96. }
  97. return *vpcs.Vpcs[0].VpcId, nil
  98. }
  99. func (s sdk) GetSubNets(ctx context.Context, vpcID string) ([]string, error) {
  100. logrus.Debug("Retrieve SubNets")
  101. subnets, err := s.EC2.DescribeSubnetsWithContext(ctx, &ec2.DescribeSubnetsInput{
  102. DryRun: nil,
  103. Filters: []*ec2.Filter{
  104. {
  105. Name: aws.String("vpc-id"),
  106. Values: []*string{aws.String(vpcID)},
  107. },
  108. {
  109. Name: aws.String("default-for-az"),
  110. Values: []*string{aws.String("true")},
  111. },
  112. },
  113. })
  114. if err != nil {
  115. return nil, err
  116. }
  117. ids := []string{}
  118. for _, subnet := range subnets.Subnets {
  119. ids = append(ids, *subnet.SubnetId)
  120. }
  121. return ids, nil
  122. }
  123. func (s sdk) GetRoleArn(ctx context.Context, name string) (string, error) {
  124. role, err := s.IAM.GetRoleWithContext(ctx, &iam.GetRoleInput{
  125. RoleName: aws.String(name),
  126. })
  127. if err != nil {
  128. return "", err
  129. }
  130. return *role.Role.Arn, nil
  131. }
  132. func (s sdk) StackExists(ctx context.Context, name string) (bool, error) {
  133. stacks, err := s.CF.DescribeStacksWithContext(ctx, &cloudformation.DescribeStacksInput{
  134. StackName: aws.String(name),
  135. })
  136. if err != nil {
  137. // FIXME doesn't work as expected
  138. return false, nil
  139. }
  140. return len(stacks.Stacks) > 0, nil
  141. }
  142. func (s sdk) CreateStack(ctx context.Context, name string, template *cf.Template, parameters map[string]string) error {
  143. logrus.Debug("Create CloudFormation stack")
  144. json, err := template.JSON()
  145. if err != nil {
  146. return err
  147. }
  148. param := []*cloudformation.Parameter{}
  149. for name, value := range parameters {
  150. param = append(param, &cloudformation.Parameter{
  151. ParameterKey: aws.String(name),
  152. ParameterValue: aws.String(value),
  153. UsePreviousValue: aws.Bool(true),
  154. })
  155. }
  156. _, err = s.CF.CreateStackWithContext(ctx, &cloudformation.CreateStackInput{
  157. OnFailure: aws.String("DELETE"),
  158. StackName: aws.String(name),
  159. TemplateBody: aws.String(string(json)),
  160. Parameters: param,
  161. TimeoutInMinutes: aws.Int64(10),
  162. Capabilities: []*string{
  163. aws.String(cloudformation.CapabilityCapabilityIam),
  164. },
  165. })
  166. return err
  167. }
  168. func (s sdk) WaitStackComplete(ctx context.Context, name string, operation int) error {
  169. input := &cloudformation.DescribeStacksInput{
  170. StackName: aws.String(name),
  171. }
  172. switch operation {
  173. case StackCreate:
  174. return s.CF.WaitUntilStackCreateCompleteWithContext(ctx, input)
  175. case StackDelete:
  176. return s.CF.WaitUntilStackDeleteCompleteWithContext(ctx, input)
  177. default:
  178. return fmt.Errorf("internal error: unexpected stack operation %d", operation)
  179. }
  180. }
  181. func (s sdk) GetStackID(ctx context.Context, name string) (string, error) {
  182. stacks, err := s.CF.DescribeStacksWithContext(ctx, &cloudformation.DescribeStacksInput{
  183. StackName: aws.String(name),
  184. })
  185. if err != nil {
  186. return "", err
  187. }
  188. return *stacks.Stacks[0].StackId, nil
  189. }
  190. func (s sdk) DescribeStackEvents(ctx context.Context, stackID string) ([]*cloudformation.StackEvent, error) {
  191. // Fixme implement Paginator on Events and return as a chan(events)
  192. events := []*cloudformation.StackEvent{}
  193. var nextToken *string
  194. for {
  195. resp, err := s.CF.DescribeStackEventsWithContext(ctx, &cloudformation.DescribeStackEventsInput{
  196. StackName: aws.String(stackID),
  197. NextToken: nextToken,
  198. })
  199. if err != nil {
  200. return nil, err
  201. }
  202. events = append(events, resp.StackEvents...)
  203. if resp.NextToken == nil {
  204. return events, nil
  205. }
  206. nextToken = resp.NextToken
  207. }
  208. }
  209. func (s sdk) DeleteStack(ctx context.Context, name string) error {
  210. logrus.Debug("Delete CloudFormation stack")
  211. _, err := s.CF.DeleteStackWithContext(ctx, &cloudformation.DeleteStackInput{
  212. StackName: aws.String(name),
  213. })
  214. return err
  215. }
  216. func (s sdk) CreateSecret(ctx context.Context, secret docker.Secret) (string, error) {
  217. logrus.Debug("Create secret " + secret.Name)
  218. secretStr, err := secret.GetCredString()
  219. if err != nil {
  220. return "", err
  221. }
  222. response, err := s.SM.CreateSecret(&secretsmanager.CreateSecretInput{
  223. Name: &secret.Name,
  224. SecretString: &secretStr,
  225. Description: &secret.Description,
  226. })
  227. if err != nil {
  228. return "", err
  229. }
  230. return *response.ARN, nil
  231. }
  232. func (s sdk) InspectSecret(ctx context.Context, id string) (docker.Secret, error) {
  233. logrus.Debug("Inspect secret " + id)
  234. response, err := s.SM.DescribeSecret(&secretsmanager.DescribeSecretInput{SecretId: &id})
  235. if err != nil {
  236. return docker.Secret{}, err
  237. }
  238. labels := map[string]string{}
  239. for _, tag := range response.Tags {
  240. labels[*tag.Key] = *tag.Value
  241. }
  242. secret := docker.Secret{
  243. ID: *response.ARN,
  244. Name: *response.Name,
  245. Labels: labels,
  246. }
  247. if response.Description != nil {
  248. secret.Description = *response.Description
  249. }
  250. return secret, nil
  251. }
  252. func (s sdk) ListSecrets(ctx context.Context) ([]docker.Secret, error) {
  253. logrus.Debug("List secrets ...")
  254. response, err := s.SM.ListSecrets(&secretsmanager.ListSecretsInput{})
  255. if err != nil {
  256. return []docker.Secret{}, err
  257. }
  258. var secrets []docker.Secret
  259. for _, sec := range response.SecretList {
  260. labels := map[string]string{}
  261. for _, tag := range sec.Tags {
  262. labels[*tag.Key] = *tag.Value
  263. }
  264. description := ""
  265. if sec.Description != nil {
  266. description = *sec.Description
  267. }
  268. secrets = append(secrets, docker.Secret{
  269. ID: *sec.ARN,
  270. Name: *sec.Name,
  271. Labels: labels,
  272. Description: description,
  273. })
  274. }
  275. return secrets, nil
  276. }
  277. func (s sdk) DeleteSecret(ctx context.Context, id string, recover bool) error {
  278. logrus.Debug("List secrets ...")
  279. force := !recover
  280. _, err := s.SM.DeleteSecret(&secretsmanager.DeleteSecretInput{SecretId: &id, ForceDeleteWithoutRecovery: &force})
  281. return err
  282. }
  283. func (s sdk) GetLogs(ctx context.Context, name string) error {
  284. logGroup := fmt.Sprintf("/docker-compose/%s", name)
  285. var startTime int64
  286. for {
  287. var hasMore = true
  288. var token *string
  289. for hasMore {
  290. events, err := s.CW.FilterLogEvents(&cloudwatchlogs.FilterLogEventsInput{
  291. LogGroupName: aws.String(logGroup),
  292. NextToken: token,
  293. StartTime: aws.Int64(startTime),
  294. })
  295. if err != nil {
  296. return err
  297. }
  298. if events.NextToken == nil {
  299. hasMore = false
  300. } else {
  301. token = events.NextToken
  302. }
  303. for _, event := range events.Events {
  304. fmt.Println(*event.Message)
  305. startTime = *event.IngestionTime
  306. }
  307. }
  308. time.Sleep(500 * time.Millisecond)
  309. }
  310. }
  311. func (s sdk) GetTasks(ctx context.Context, cluster string, name string) ([]string, error) {
  312. tasks, err := s.ECS.ListTasksWithContext(ctx, &ecs.ListTasksInput{
  313. Cluster: aws.String(cluster),
  314. ServiceName: aws.String(name),
  315. })
  316. if err != nil {
  317. return nil, err
  318. }
  319. arns := []string{}
  320. for _, arn := range tasks.TaskArns {
  321. arns = append(arns, *arn)
  322. }
  323. return arns, nil
  324. }
  325. func (s sdk) GetNetworkInterfaces(ctx context.Context, cluster string, arns ...string) ([]string, error) {
  326. tasks, err := s.ECS.DescribeTasksWithContext(ctx, &ecs.DescribeTasksInput{
  327. Cluster: aws.String(cluster),
  328. Tasks: aws.StringSlice(arns),
  329. })
  330. if err != nil {
  331. return nil, err
  332. }
  333. interfaces := []string{}
  334. for _, task := range tasks.Tasks {
  335. for _, attachement := range task.Attachments {
  336. if *attachement.Type == "ElasticNetworkInterface" {
  337. for _, pair := range attachement.Details {
  338. if *pair.Name == "networkInterfaceId" {
  339. interfaces = append(interfaces, *pair.Value)
  340. }
  341. }
  342. }
  343. }
  344. }
  345. return interfaces, nil
  346. }
  347. func (s sdk) GetPublicIPs(ctx context.Context, interfaces ...string) ([]string, error) {
  348. desc, err := s.EC2.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{
  349. NetworkInterfaceIds: aws.StringSlice(interfaces),
  350. })
  351. if err != nil {
  352. return nil, err
  353. }
  354. publicIPs := []string{}
  355. for _, interf := range desc.NetworkInterfaces {
  356. publicIPs = append(publicIPs, *interf.Association.PublicIp)
  357. }
  358. return publicIPs, nil
  359. }