text: /**
   * Copyright 2013-2020 the original author or authors from the JHipster project.
   *
   * This file is part of the JHipster project, see https://www.jhipster.tech/
   * for more information.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  const _ = require('lodash');
  const fs = require('fs');
  const chalk = require('chalk');
  const AWS = require('aws-sdk');
  const ProgressBar = require('progress');
  const ora = require('ora');
  
  const utils = require('../utils');
  
  const AwsSSM = require('./lib/ssm');
  const AwsECR = require('./lib/ecr');
  const AwsCF = require('./lib/cloudFormation');
  
  const DEFAULT_REGION = 'us-east-1';
  const S3_MIN_PART_SIZE = 5242880;
  
  let credentials;
  let ec2;
  // let ecr;
  let s3;
  let sts;
  
  // Instances from ./lib. Composed with aws-sdk
  let SSM;
  let ECR;
  let CF;
  
  module.exports = {
      DEFAULT_REGION,
      SSM: () => SSM,
      ECR: () => ECR,
      CF: () => CF,
      createS3Bucket,
      getDockerLogin,
      listRegions,
      listSubnets,
      listVpcs,
      saveCredentialsInAWS,
      initAwsStuff,
      sanitizeBucketName,
      uploadTemplate,
  };
  
  /**
   * Init AWS stuff like ECR and whatnot.
   *
   * @param ecrConfig The config used to instanciate ECR
   */
  function initAwsStuff(region = DEFAULT_REGION) {
      ec2 = new AWS.EC2({ region });
      // ecr = new AWS.ECR({ region });
      s3 = new AWS.S3();
      sts = new AWS.STS();
  
      SSM = new AwsSSM(region);
      ECR = new AwsECR(region);
      CF = new AwsCF(region);
  }
  
  /**
   * Wraps the promise in a CLI spinner
   * @param promise
   */
  function spinner(promise, text = 'loading', spinnerIcon = 'monkey') {
      const spinner = ora({ spinner: spinnerIcon, text }).start();
      return new Promise((resolve, reject) => {
          promise
              .then(resolved => {
                  spinner.stop();
                  resolve(resolved);
              })
              .catch(err => {
                  spinner.stop();
                  reject(err);
              });
      });
  }
  
  /**
   * listRegions() returns a Promise, which resolves to an array of AWS region objects,
   * with "Endpoint" and "RegionName" properties
   *
   * @param region to use. Defaults to us-east-1
   * @returns {Promise<EC2.Region[]>}
   */
  function listRegions() {
      return spinner(
          ec2
              .describeRegions({})
              .promise()
              .then(data => data.Regions)
      );
  }
  
  /**
   * listVpcs() returns a Promise, which resolves to an array of AWS VPC objects
   * @returns {Promise<EC2.Vpc[]>}
   */
  function listVpcs() {
      return spinner(
          ec2
              .describeVpcs({})
              .promise()
              .then(data => data.Vpcs)
      );
  }
  
  /**
   * listSubnets() returns a Promise, which resolves to an array of
   * Subnets available within the supplied VPC ID
   * @param vpcId of the VPC with the subnets
   * @returns {Promise<EC2.Subnet[]>}
   */
  function listSubnets(vpcId) {
      const params = {
          Filters: [
              {
                  Name: 'vpc-id',
                  Values: [vpcId],
              },
              {
                  Name: 'state',
                  Values: ['available'],
              },
          ],
      };
      return spinner(
          ec2
              .describeSubnets(params)
              .promise()
              .then(data => data.Subnets)
      );
  }
  
  /**
   * Get the credentials from the ~/.aws/credentials file using the AWS_PROFILE env var to get the profile.
   *
   * @param profile The AWS profile to get the credentials from. Default to 'default'
   * @returns {Promise} Will resolve with no parameters if it succeeds, rejects with the error if it fails (no credentials found for given profile.
   */
  function saveCredentialsInAWS(profile = 'default') {
      credentials = new AWS.SharedIniFileCredentials({ profile });
      return new Promise((resolve, reject) =>
          credentials.refresh(err => {
              if (err) {
                  reject(err);
              }
              AWS.config.credentials = credentials;
              resolve();
          })
      );
  }
  
  /**
   * Retrieve decoded information to authenticate to Docker with AWS credentials.
   * @returns {Promise} Returns a promise that resolves when the informations are retrieved.
   */
  function getDockerLogin() {
      return spinner(
          new Promise((resolve, reject) =>
              _getAuthorizationToken().then(authToken =>
                  sts
                      .getCallerIdentity({})
                      .promise()
                      .then(data => {
                          const decoded = utils.decodeBase64(authToken.authorizationToken);
                          const splitResult = decoded.split(':');
                          resolve({
                              username: splitResult[0],
                              password: splitResult[1],
                              accountId: data.Account,
                          });
                      })
                      .catch(() => reject(new Error("Couldn't retrieve the user informations")))
              )
          )
      );
  }
  
  /**
   * Fetch Authentication token from AWS to authenticate with Docker
   * @returns {Promise} Returns a promise that resolves when the informations are retrieved.
   * @private
   */
  function _getAuthorizationToken() {
      return spinner(
          new Promise((resolve, reject) =>
              ECR.sdk
                  .getAuthorizationToken({})
                  .promise()
                  .then(data => {
                      if (!_.has(data, 'authorizationData.0')) {
                          reject(new Error('No authorization data found.'));
                          return;
                      }
                      resolve(data.authorizationData[0]);
                  })
          )
      );
  }
  
  /**
   * Create a S3 Bucket in said region with said bucketBaseName.
   * the bucketBaseName will be used to create a
   * @param bucketName the name of the bucket to create.
   * @param region the region to create the bucket in.
   * @returns {Promise}
   */
  function createS3Bucket(bucketName, region = DEFAULT_REGION) {
      const createBuckerParams = {
          Bucket: bucketName,
      };
      return spinner(
          new Promise((resolve, reject) =>
              s3
                  .headBucket({
                      Bucket: bucketName,
                  })
                  .promise()
                  .catch(error => {
                      if (error.code !== 'NotFound') {
                          reject(
                              new Error(
                                  `The S3 Bucket ${chalk.bold(bucketName)} in region ${chalk.bold(
                                      region
                                  )} already exists and you don't have access to it. Error code: ${chalk.bold(error.code)}`
                              )
                          );
                      }
                  })
                  .then(() =>
                      s3
                          .createBucket(createBuckerParams)
                          .promise()
                          .then(resolve)
                          .catch(error =>
                              reject(
                                  new Error(
                                      `There was an error during the creation of the S3 Bucket ${chalk.bold(
                                          bucketName
                                      )} in region ${chalk.bold(region)}`
                                  )
                              )
                          )
                  )
          )
      );
  }
  
  /**
   * Upload the template in the S3Bucket
   * @param bucketName S3 Bucket name to upload the template into
   * @param filename Name to give to the file in the Bucket
   * @param path Path to the file
   * @returns {Promise}
   */
  function uploadTemplate(bucketName, filename, path) {
      return spinner(
          new Promise((resolve, reject) =>
              fs.stat(path, (error, stats) => {
                  if (!stats) {
                      reject(new Error(`File ${chalk.bold(path)} not found`));
                  }
                  const upload = s3.upload(
                      {
                          Bucket: bucketName,
                          Key: filename,
                          Body: fs.createReadStream(path),
                      },
                      {
                          partSize: Math.max(stats.size, S3_MIN_PART_SIZE),
                          queueSize: 1,
                      }
                  );
                  let bar;
                  upload.on('httpUploadProgress', evt => {
                      if (!bar && evt.total) {
                          const total = evt.total / 1000000;
                          bar = new ProgressBar('uploading [:bar] :percent :etas', {
                              complete: '=',
                              incomplete: ' ',
                              width: 20,
                              total,
                              clear: true,
                          });
                      }
  
                      const curr = evt.loaded / 1000000;
                      bar.tick(curr - bar.curr);
                  });
                  return upload.promise().then(resolve).catch(reject);
              })
          )
      );
  }
  
  /**
   * Sanitize the bucketName following the rule found here:
   * http://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-s3-bucket-naming-requirements.html
   * @param bucketName
   * @returns {string}
   */
  function sanitizeBucketName(bucketName) {
      return _(bucketName)
          .split('.')
          .filter(e => e)
          .map(_.toLower)
          .map(e => _.replace(e, '_', '-'))
          .join('.');
  }
  