// Copyright Abridged, Inc. 2021,2024. All Rights Reserved. // Node module: @collabland/aws // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT import { DescribeEnvironmentResourcesCommand, DescribeEnvironmentResourcesCommandInput, DescribeInstancesHealthCommand, DescribeInstancesHealthCommandInput, ElasticBeanstalkClient, ElasticBeanstalkClientConfig, InstancesHealthAttribute, } from '@aws-sdk/client-elastic-beanstalk'; import {getFetch, loggers} from '@collabland/common'; import {getDefaultConfig} from '../common/types.js'; const {trace} = loggers('collabland:aws:elastic-beanstalk'); export class ElasticBeanstalkServiceClient { readonly client: ElasticBeanstalkClient; constructor(options?: ElasticBeanstalkClientConfig) { this.client = new ElasticBeanstalkClient({ ...getDefaultConfig(), ...options, }); } /** * Describe instances health * @param environmentName - Elastic Beanstalk environment name * @returns */ async describeHealth(environmentName: string) { trace('Describing environment health for %s', environmentName); const input: DescribeInstancesHealthCommandInput = { EnvironmentName: environmentName, AttributeNames: [InstancesHealthAttribute.All], }; const cmd = new DescribeInstancesHealthCommand(input); const output = await this.client.send(cmd); trace('Environment health: %j', output); return output; } /** * Describe resources * @param environmentName - Elastic Beanstalk environment name * @returns */ async describeResources(environmentName: string) { trace('Describing environment health for %s', environmentName); const input: DescribeEnvironmentResourcesCommandInput = { EnvironmentName: environmentName, }; const cmd = new DescribeEnvironmentResourcesCommand(input); const output = await this.client.send(cmd); trace('Environment resources: %j', output); return output; } /** * Get an array of EC2 instance ids * @param environmentName - Elastic Beanstalk environment name * @returns */ async getEC2InstanceIds(environmentName: string) { const instances = (await this.describeHealth(environmentName)) .InstanceHealthList; return ( instances?.filter(i => i.HealthStatus === 'Ok').map(i => i.InstanceId!) ?? [] ); } private metadataToken: string | undefined; private expiresAt: number | undefined; async getEC2MetadataToken() { if ( this.metadataToken != null && this.expiresAt != null && this.expiresAt > Date.now() + 600 * 1000 ) { return this.metadataToken; } const fetch = getFetch({timeout: 1000}); const res = await fetch('http://169.254.169.254/latest/api/token', { method: 'put', headers: { 'x-aws-ec2-metadata-token-ttl-seconds': '21600', }, }); const token = await res.text(); this.expiresAt = Date.now() + 21600 * 1000; this.metadataToken = token; return token; } /** * Get the current EC2 instance id for the Elastic Beanstalk application * @returns */ // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html async getEC2InstanceId() { const fetch = getFetch({timeout: 1000}); const token = await this.getEC2MetadataToken(); const result = await fetch( 'http://169.254.169.254/latest/meta-data/instance-id', { headers: { 'x-aws-ec2-metadata-token': token, }, }, ); const id = await result.text(); return id; } }