#!/bin/bash
# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# A copy of the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "LICENSE.txt" file accompanying this file.
# This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied.
# See the License for the specific language governing permissions and limitations under the License.
echo "preinstall-$1" > /tmp/preinstall_out.txt
echo "preinstall run OK"


#!/bin/bash
# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# A copy of the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "LICENSE.txt" file accompanying this file.
# This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied.
# See the License for the specific language governing permissions and limitations under the License.
df -h | grep '/$' | awk '{{print $2}}' > /shared/$(hostname)_root_volume_size.txt


#!/bin/bash
# Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# A copy of the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "LICENSE.txt" file accompanying this file.
# This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied.
# See the License for the specific language governing permissions and limitations under the License.
echo "updated_postinstall-$1" > /tmp/updated_postinstall_out.txt
echo "updated_postinstall run OK"


#!/bin/bash
# Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# A copy of the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "LICENSE.txt" file accompanying this file.
# This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied.
# See the License for the specific language governing permissions and limitations under the License.
echo "starting updated_postupdate script"
echo "updated_postupdate-$1" > /tmp/updated_postupdate_out.txt
echo "updated_postupdate run OK"


#!/bin/bash
# Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# A copy of the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "LICENSE.txt" file accompanying this file.
# This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied.
# See the License for the specific language governing permissions and limitations under the License.
echo "updated_preinstall-$1" > /tmp/updated_preinstall_out.txt
echo "updated_preinstall run OK"


#!/bin/bash

set -ex

# On Mac OS, the default implementation of sed is BSD sed, but this script requires GNU sed.
if [ "$(uname)" == "Darwin" ]; then
  command -v gsed >/dev/null 2>&1 || { echo >&2 "[ERROR] Mac OS detected: please install GNU sed with 'brew install gnu-sed'"; exit 1; }
  PATH="/usr/local/opt/gnu-sed/libexec/gnubin:$PATH"
fi

if [ -z "$1" ]; then
    echo "New version not specified. Usage: bump-awsbatch-cli-version.sh NEW_VERSION"
    exit 1
fi

NEW_VERSION=$1
CURRENT_VERSION=$(sed -ne "s/^VERSION = \"\(.*\)\"/\1/p" awsbatch-cli/setup.py)
sed -i "s/VERSION = \"$CURRENT_VERSION\"/VERSION = \"$NEW_VERSION\"/g" awsbatch-cli/setup.py


#!/bin/bash

set -ex

# On Mac OS, the default implementation of sed is BSD sed, but this script requires GNU sed.
if [ "$(uname)" == "Darwin" ]; then
  command -v gsed >/dev/null 2>&1 || { echo >&2 "[ERROR] Mac OS detected: please install GNU sed with 'brew install gnu-sed'"; exit 1; }
  PATH="/usr/local/opt/gnu-sed/libexec/gnubin:$PATH"
fi

_error_exit() {
   echo "$1"
   exit 1
}

_help() {
    local -- _cmd=$(basename "$0")

    cat <<EOF

  Usage: ${_cmd} [OPTION]...

  Bump ParallelCluster version.

  --version <version>                                               ParallelCluster version
  -h, --help                                                        Print this help message
EOF
}

main() {
    # parse input options
    while [ $# -gt 0 ] ; do
        case "$1" in
            --version)                            _version="$2"; shift;;
            --version=*)                          _version="${1#*=}";;
            -h|--help|help)                       _help; exit 0;;
            *)                                    _help; _error_exit "[error] Unrecognized option '$1'";;
        esac
        shift
    done

    # verify required parameters
    if [ -z "${_version}" ]; then
        _error_exit "--version parameter not specified"
        _help;
    else
        NEW_VERSION=$_version
        NEW_VERSION_SHORT=$(echo ${NEW_VERSION} | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+")
        CURRENT_VERSION=$(sed -ne "s/^VERSION = \"\(.*\)\"/\1/p" cli/setup.py)
        CURRENT_VERSION_SHORT=$(echo ${CURRENT_VERSION} | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+")
        PC_SUPPORT_DIR="./pc_support"
        sed -i "s/VERSION = \"$CURRENT_VERSION\"/VERSION = \"$NEW_VERSION\"/g" cli/setup.py

        sed -i "s/\"parallelcluster\": \"$CURRENT_VERSION\"/\"parallelcluster\": \"$NEW_VERSION\"/g" cli/src/pcluster/constants.py
        sed -i "s/aws-parallelcluster-cookbook-$CURRENT_VERSION/aws-parallelcluster-cookbook-$NEW_VERSION/g" cli/src/pcluster/constants.py
        sed -i "s| version: $CURRENT_VERSION_SHORT| version: $NEW_VERSION_SHORT|g" cli/src/pcluster/api/openapi/openapi.yaml

        sed -i "s|parallelcluster/$CURRENT_VERSION|parallelcluster/$NEW_VERSION|g" api/infrastructure/parallelcluster-api.yaml
        sed -i "s| Version: $CURRENT_VERSION| Version: $NEW_VERSION|g" api/infrastructure/parallelcluster-api.yaml
        sed -i "s| ShortVersion: $CURRENT_VERSION_SHORT| ShortVersion: $NEW_VERSION_SHORT|g" api/infrastructure/parallelcluster-api.yaml
        sed -i "s| version: $CURRENT_VERSION_SHORT| version: $NEW_VERSION_SHORT|g" api/spec/openapi/ParallelCluster.openapi.yaml
        sed -i "s| version: \"$CURRENT_VERSION_SHORT\"| version: \"$NEW_VERSION_SHORT\"|g" api/spec/smithy/model/parallelcluster.smithy
        sed -i "s| Version: $CURRENT_VERSION| Version: $NEW_VERSION|g" cloudformation/custom_resource/cluster.yaml
        sed -i "s| Version: $CURRENT_VERSION| Version: $NEW_VERSION|g" cloudformation/custom_resource/cluster-1-click.yaml
        cp "$PC_SUPPORT_DIR/os_$CURRENT_VERSION.json" "$PC_SUPPORT_DIR/os_$NEW_VERSION.json"
        git add "$PC_SUPPORT_DIR/os_$NEW_VERSION.json"

        pushd api && ./gradlew generatePythonClient && popd
    fi
}

main "$@"

# vim:syntax=sh


#!/bin/bash

set -e -o xtrace

append_package_details_to_final_license_file(){
  # Function to Append Package Details to the THIRD-PARTY-LICENSES file
  #Arguments ->  1- Package Name, 2- Package Version, 3- License Type , 4- URL for package, 5,6,7- URL for License
  # Adding a header to final License file with Package Name, Package Version, License Type , URL for package
  echo -e "\n\n\n$1 \n$2 \n$3 \n$4" >> $final_license_file
  # Appending License
  curl $5 >> $final_license_file
  # Adding Dual Licenses if they exist
  if [ $# -gt 5 ]
    then
      curl $6 >> $final_license_file
      curl $7 >> $final_license_file
  fi

}

function create_attribution_doc() {
  ATTR_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"

  # Install the python version if it doesnt exist
  if test ! -d ${PYENV_ROOT}/versions/${PYTHON_VERSION};
  then
    env PYTHON_CONFIGURE_OPTS="--enable-shared" pyenv install ${PYTHON_VERSION}
  fi

  pyenv virtualenv ${PYTHON_VERSION} attribution-doc-env
  # switch to a specific virtual env
  source ${PYENV_ROOT}/versions/attribution-doc-env/bin/activate

  # Update Pip
  pip3 install --upgrade pip

  # Installing PyInstaller
  pip3 install pyinstaller
  # Install pip-licenses
  pip3 install pip-licenses

  # install pcluster via source

  pip3 install -e $(dirname $ATTR_SCRIPT_DIR )/cli
  #pip3 install -r requirements.txt

  final_license_file=$(dirname $ATTR_SCRIPT_DIR )/THIRD-PARTY-LICENSES.txt

  # Create a list of aws cdk Sub-packages we want to ignore
  aws_cdk_ignore_subpackages=$(pip list | grep cdk | awk '{print $1}')
  aws_cdk_version=$(pip list | grep cdk | awk '{print $2}'| head -n 1)

  packaging_version=$(pip list | grep packaging | awk '{print $2}')
  exception_group_version=$(pip list | grep exceptiongroup | awk '{print $2}')
  idna_version=$(pip list | grep idna | awk '{print $2}')
  typing_extentions_version=$(pip list | grep typing_extensions | awk '{print $2}')
  # Create a pip License document
  pip-licenses -i $aws_cdk_ignore_subpackages aws-parallelcluster pip-licenses packaging pyinstaller-hooks-contrib pyinstaller exceptiongroup idna typing_extensions certifi --format=plain-vertical --with-license-file --with-urls --no-license-path --with-authors --output-file=$final_license_file

  #Getting python version
  cpy_version=$(python -V |  grep -Eo '([0-9]+)(\.?[0-9]+)' | head -1)

  # certifi
  append_package_details_to_final_license_file "certifi" "2022.12.07" "Mozilla Public License 2.0 (MPL 2.0)" "https://github.com/certifi/python-certifi/tree/2022.12.07" "https://raw.githubusercontent.com/certifi/python-certifi/2022.12.07/LICENSE"

  # exception_group
  append_package_details_to_final_license_file "exceptiongroup" $exception_group_version "MIT License" "https://github.com/agronholm/exceptiongroup" "https://raw.githubusercontent.com/agronholm/exceptiongroup/main/LICENSE"

  # idna
  append_package_details_to_final_license_file "idna" $idna_version "BSD License" "https://github.com/kjd/idna" "https://raw.githubusercontent.com/kjd/idna/master/LICENSE.md"

  # typing_extentions
  append_package_details_to_final_license_file "typing_extensions" $typing_extentions_version "Python Software Foundation License" "https://github.com/python/typing_extensions" "https://raw.githubusercontent.com/python/typing_extensions/main/LICENSE"

  # Packaging
  append_package_details_to_final_license_file "packaging" $packaging_version "Apache Software License; BSD License" "https://github.com/pypa/packaging" "https://raw.githubusercontent.com/pypa/packaging/main/LICENSE" "https://raw.githubusercontent.com/pypa/packaging/main/LICENSE.APACHE" "https://raw.githubusercontent.com/pypa/packaging/main/LICENSE.BSD"

  # pyinstaller-hooks-contrib
  append_package_details_to_final_license_file "pyinstaller-hooks-contrib" "2022.15" "Apache License 2.0; GNU General Public License" "https://github.com/pyinstaller/pyinstaller-hooks-contrib/archive/refs/tags/2022.15.tar.gz." "https://raw.githubusercontent.com/pyinstaller/pyinstaller-hooks-contrib/2022.15/LICENSE" "https://raw.githubusercontent.com/pyinstaller/pyinstaller-hooks-contrib/2022.15/LICENSE.APL.txt" "https://raw.githubusercontent.com/pyinstaller/pyinstaller-hooks-contrib/2022.15/LICENSE.GPL.txt"

  # pyinstaller
  append_package_details_to_final_license_file "pyinstaller" "5.7.0" "GNU General Public License v2 (GPLv2)" "https://github.com/pyinstaller/pyinstaller/archive/refs/tags/v5.7.0.tar.gz." "https://raw.githubusercontent.com/pyinstaller/pyinstaller/v5.7.0/COPYING.txt"

  # aws-cdk
  append_package_details_to_final_license_file "aws-cdk" $aws_cdk_version "Apache License" "https://raw.githubusercontent.com/aws/aws-cdk/main/LICENSE" "https://raw.githubusercontent.com/aws/aws-cdk/main/LICENSE"

  # Python
  append_package_details_to_final_license_file "Python" $cpy_version "PSF License Version 2; Zero-Clause BSD license" "https://raw.githubusercontent.com/python/cpython/$cpy_version/LICENSE" "https://raw.githubusercontent.com/python/cpython/$cpy_version/LICENSE"

  deactivate
  pyenv virtualenv-delete -f attribution-doc-env

}

_error_exit() {
   echo "$1"
   exit 1
}

_help() {
    local -- _cmd=$(basename "$0")

    cat <<EOF
  This script will create the THIRD_PARTY_LICENSE.txt file assuming you have already installed Pyenv
  Usage: ${_cmd} [OPTION]...

  --python-version <version>                                        Python version with which you want to create the attribution document.
  -h, --help                                                        Print this help message

  Examples:
  ${_cmd}
  $_cmd --python-version 3.9.10
EOF
}

function parse_options () {

   while [ $# -gt 0 ] ; do
          case "$1" in
              --python-version)                      PYTHON_VERSION="$2"; shift;;
              -h|--help|help)                       _help; exit 0;;
              *)                                    _help; _error_exit "[error] Unrecognized option '$1'";;
          esac
          shift
      done

}

function main() {
  parse_options "$@"
  create_attribution_doc
}

main "$@"


#!/bin/bash

_error_exit() {
   echo "$1"
   exit 1
}

_info() {
  echo "INFO: $1"
}

_help() {
    local -- _cmd=$(basename "$0")

    cat <<EOF

  Usage: ${_cmd} [OPTION]...

  Copy the AWS ParallelCluster Package to an S3 bucket.

  --bucket <bucket>             Bucket to upload the package to
  --srcdir <src-dir>            Root folder of the pcluster project
  --profile <aws-profile>       AWS profile name to use for the upload
                                (optional, default is AWS_PROFILE env variable or "default").
  --region <aws-region>         Region to use for AWSCli commands (optional, default is "us-east-1")
  -h, --help                    Print this help message
EOF
}

main() {
    # parse input options
    while [ $# -gt 0 ] ; do
        case "$1" in
            --bucket)           _bucket="$2"; shift;;
            --bucket=*)         _bucket="${1#*=}";;
            --srcdir)           _srcdir="$2"; shift;;
            --srcdir=*)         _srcdir="${1#*=}";;
            --profile)          _profile="$2"; shift;;
            --profile=*)        _profile="${1#*=}";;
            --region)           _region="$2"; shift;;
            --region=*)         _region="${1#*=}";;
            -h|--help|help)     _help; exit 0;;
            *)                  _help; echo "[error] Unrecognized option '$1'"; exit 1;;
        esac
        shift
    done

    # verify required parameters
    if [ -z "${_bucket}" ]; then
        _error_exit "--bucket parameter not specified"
        _help;
    fi
    if [ -z "${_srcdir}" ]; then
        _error_exit "--srcdir parameter not specified"
        _help;
    fi

    # initialize optional parameters
    if [ -z "${AWS_PROFILE}" ] && [ -z "${_profile}" ]; then
        _info "--profile parameter not specified, using 'default'"
    elif [ -n "${_profile}" ]; then
        _profile="--profile ${_profile}"
    fi
    if [ -z "${_region}" ]; then
        _info "--region parameter not specified, using 'us-east-1'"
        _region="us-east-1"
    fi

    # check bucket or create it
    aws ${_profile} s3api head-bucket --bucket "${_bucket}" --region "${_region}"
    if [ $? -ne 0 ]; then
        _info "Bucket ${_bucket} does not exist, trying to create it"
        aws ${_profile} s3api create-bucket --bucket "${_bucket}" --region "${_region}"
        if [ $? -ne 0 ]; then
            _error_exit "Unable to create bucket ${_bucket}"
        fi
    fi

    _pcluster_version=$(grep "^VERSION = \"" "${_srcdir}/cli/setup.py" |awk '{print $3}'| tr -d \")
    if [ -z "${_pcluster_version}" ]; then
        _error_exit "Unable to detect ParallelCluster CLI version, are you in the right directory?"
    fi
    _info "Detected ParallelCluster CLI version ${_pcluster_version}"

    _version=$(grep "^VERSION = \"" "${_srcdir}/awsbatch-cli/setup.py" |awk '{print $3}'| tr -d \")
    if [ -z "${_version}" ]; then
        _error_exit "Unable to detect ParallelCluster AWS Batch CLI version, are you in the right directory?"
    fi
    _info "Detected ParallelCluster AWS Batch CLI version ${_version}"

    # Create archive
    _cwd=$(pwd)
    pushd "${_srcdir}" > /dev/null
    _stashName=$(git stash create)
    git archive --format tar --prefix="aws-parallelcluster-${_pcluster_version}/" "${_stashName:-HEAD}" | gzip > "${_cwd}/aws-parallelcluster-${_pcluster_version}.tgz"
    popd > /dev/null

    # upload package
    _key_path="parallelcluster/${_pcluster_version}/cli"
    aws ${_profile} --region "${_region}" s3 cp aws-parallelcluster-${_pcluster_version}.tgz s3://${_bucket}/${_key_path}/aws-parallelcluster-${_pcluster_version}.tgz || _error_exit 'Failed to push CLI to S3'

    _bucket_region=$(aws ${_profile} s3api get-bucket-location --bucket ${_bucket} --output text)
    if [ ${_bucket_region} == "None" ]; then
        _bucket_region=""
     else
         _bucket_region=".${_bucket_region}"
     fi

    echo "Done. Add the following configuration to the pcluster create config file:"
    echo ""
    echo "DevSettings:"
    echo "  AwsBatchCliPackage: s3://${_bucket}/${_key_path}/aws-parallelcluster-${_pcluster_version}.tgz"
}

main "$@"

# vim:syntax=sh


#!/bin/bash

source kitchen/kitchen.local-yml.sh

THIS_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
export KITCHEN_YAML="${THIS_DIR}/kitchen.docker.yml"
export KITCHEN_GLOBAL_YAML="${THIS_DIR}/kitchen.global.yml"
export KITCHEN_DRIVER=dokken

# Export DOCKER_HOST variable when using Colima
if [[ $(command -v colima) ]]; then
  colima status || colima start
  export DOCKER_HOST=unix://${HOME}/.colima/default/docker.sock
fi

echo "** KITCHEN_LOCAL_YAML: $KITCHEN_LOCAL_YAML"
echo "** KITCHEN_YAML: $KITCHEN_YAML"
echo "** KITCHEN_GLOBAL_YAML: $KITCHEN_GLOBAL_YAML"
echo "kitchen $*"

kitchen "$@"


#!/bin/bash

# Variables you can set in your local .kitchen.env.sh (not to be pushed)
#
# KITCHEN_AWS_REGION:         AWS region;
#                             falls back to AWS_DEFAULT_REGION, then to eu-west-1
#
# KITCHEN_ARCHITECTURE:       [x86_64|arm64];
#                             default x86_64
#
# KITCHEN_INSTANCE_TYPE:      instance type to use
#                             default t2.micro
#
# KITCHEN_KEY_NAME:           KeyPair to use with EC2 instances
#                             default kitchen
#
# KITCHEN_SUBNET_ID:          subnet to put EC2 instance into
#                             if not set will use Subnet tagged with Kitchen=true
#
# KITCHEN_SECURITY_GROUP_ID:  security group to associate to the instance
#                             if not set will use SG tagged with Kitchen=true
#
# KITCHEN_IAM_PROFILE:        IAM instance profile name
#                             if not set no profile will be attached
#
# KITCHEN_USER_DATA_SCRIPT:   user-data script to launch on the instance
#                             if not set no user-data script will be launched
#
# KITCHEN_SSH_KEY_PATH:       path to private key
#                             default ~/.ssh/${key-name}-${region}.pem
#
# KITCHEN_PCLUSTER_VERSION:   ParallelCluster version to use
#                             defaults to the latest one
#
# KITCHEN_ALINUX2_AMI:        specific AMI to use for alinux2
#                             if not specified, will look for the latest suitable ParallelCluster AMI
#
# KITCHEN_RHEL8_AMI:        specific AMI to use for redhat8
#                             if not specified, will look for the latest suitable ParallelCluster AMI
#
# KITCHEN_CENTOS7_AMI:        specific AMI to use for centos7
#                             if not specified, will look for the latest suitable ParallelCluster AMI
#
# KITCHEN_UBUNTU2004_AMI:       specific AMI to use for ubuntu20.04
#                             if not specified, will look for the latest suitable ParallelCluster AMI
#
# KITCHEN_UBUNTU2204_AMI:       specific AMI to use for ubuntu22.04
#                             if not specified, will look for the latest suitable ParallelCluster AMI
#

source kitchen/kitchen.local-yml.sh

THIS_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )

export KITCHEN_YAML="${THIS_DIR}/kitchen.ec2.yml"
export KITCHEN_GLOBAL_YAML="${THIS_DIR}/kitchen.global.yml"
export KITCHEN_DRIVER=ec2

if [ -e "${THIS_DIR}/.kitchen.env.sh" ]
then
  echo "*** Apply local environment"
  source "${THIS_DIR}/.kitchen.env.sh"
fi

: "${KITCHEN_AWS_REGION:=${AWS_DEFAULT_REGION:-eu-west-1}}"
: "${KITCHEN_KEY_NAME:=kitchen}"
: "${KITCHEN_SSH_KEY_PATH:="~/.ssh/${KITCHEN_KEY_NAME}-${KITCHEN_AWS_REGION}.pem"}"
: "${KITCHEN_AVAILABILITY_ZONE:=a}"
: "${KITCHEN_ARCHITECTURE:=x86_64}"

if [ "$1" == "create" ] || [ "$1" == "converge" ] || [ "$1" == "verify" ] || [ "$1" == "destroy" ] || [ "$1" == "test" ]; then
  # Subnet
  if [ -z "${KITCHEN_SUBNET_ID}" ]; then
    echo "** KITCHEN_SUBNET_ID not explicitly set: looking for subnet tagged Kitchen=true"

    KITCHEN_SUBNET_ID=$(aws ec2 describe-subnets --region "${KITCHEN_AWS_REGION}" \
        --filters "Name=tag:Kitchen,Values=true" \
                  "Name=availability-zone,Values=${KITCHEN_AWS_REGION}${KITCHEN_AVAILABILITY_ZONE}" \
        --query 'Subnets[0].SubnetId' --output text)

    echo "** KITCHEN_SUBNET_ID: ${KITCHEN_SUBNET_ID}"

    if [ "${KITCHEN_SUBNET_ID}" = "None" ]; then
      echo "Subnet tagged Kitchen=true not found in AZ ${KITCHEN_AWS_REGION}${KITCHEN_AVAILABILITY_ZONE}"
      exit 1
    fi
  fi

  # VPC
  if [ -z "${KITCHEN_VPC_ID}" ]; then
    echo "** KITCHEN_VPC_ID not explicitly set: deriving from subnet"

    KITCHEN_VPC_ID=$(aws ec2 describe-subnets --region "${KITCHEN_AWS_REGION}" \
        --subnet-ids "${KITCHEN_SUBNET_ID}" \
        --query 'Subnets[0].VpcId' --output text)

    echo "** KITCHEN_VPC_ID: ${KITCHEN_VPC_ID}"
  fi

  # Security Group
  if [ -z "${KITCHEN_SECURITY_GROUP_ID}" ]; then
    echo "** KITCHEN_SECURITY_GROUP_ID not explicitly set"

    if [ -z "${KITCHEN_SECURITY_GROUP_NAME}" ]; then
      echo "** KITCHEN_SECURITY_GROUP_NAME not explicitly set, looking for tag Kitchen=true"

      KITCHEN_SECURITY_GROUP_ID=$(aws ec2 describe-security-groups --region "${KITCHEN_AWS_REGION}" \
                  --filters "Name=tag:Kitchen,Values=true" "Name=vpc-id,Values=${KITCHEN_VPC_ID}" \
                  --query 'SecurityGroups[0].GroupId' --output text)
    else
      echo "** Looking for SG named ${KITCHEN_SECURITY_GROUP_NAME}"

      KITCHEN_SECURITY_GROUP_ID=$(aws ec2 describe-security-groups --region "${KITCHEN_AWS_REGION}" \
                  --filters "Name=vpc-id,Values=${KITCHEN_VPC_ID}" "Name=group-name,Values=${KITCHEN_SECURITY_GROUP_NAME}" \
                  --query 'SecurityGroups[0].GroupId' --output text)
    fi

    echo "** KITCHEN_SECURITY_GROUP_ID: ${KITCHEN_SECURITY_GROUP_ID}"

  fi
fi

export KITCHEN_AWS_REGION
export KITCHEN_KEY_NAME
export KITCHEN_SSH_KEY_PATH
export KITCHEN_AVAILABILITY_ZONE
export KITCHEN_ARCHITECTURE
export KITCHEN_INSTANCE_TYPE
export KITCHEN_SUBNET_ID
export KITCHEN_VPC_ID
export KITCHEN_SECURITY_GROUP_ID

echo "** KITCHEN_AWS_REGION: ${KITCHEN_AWS_REGION}"
echo "** KITCHEN_KEY_NAME: ${KITCHEN_KEY_NAME}"
echo "** KITCHEN_SSH_KEY_PATH: ${KITCHEN_SSH_KEY_PATH}"
echo "** KITCHEN_AVAILABILITY_ZONE: ${KITCHEN_AVAILABILITY_ZONE}"
echo "** KITCHEN_ARCHITECTURE: ${KITCHEN_ARCHITECTURE}"
echo "** KITCHEN_INSTANCE_TYPE: ${KITCHEN_INSTANCE_TYPE}"
echo "** KITCHEN_SUBNET_ID: ${KITCHEN_SUBNET_ID}"
echo "** KITCHEN_VPC_ID: ${KITCHEN_VPC_ID}"
echo "** KITCHEN_SECURITY_GROUP_ID: ${KITCHEN_SECURITY_GROUP_ID}"
echo "** KITCHEN_IAM_PROFILE: ${KITCHEN_IAM_PROFILE}"
echo "** KITCHEN_LOCAL_YAML: ${KITCHEN_LOCAL_YAML}"
echo "** KITCHEN_YAML: $KITCHEN_YAML"
echo "** KITCHEN_GLOBAL_YAML: $KITCHEN_GLOBAL_YAML"
echo "kitchen $*"

kitchen "$@"


#!/bin/bash

# Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the
# License. A copy of the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
# OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
# limitations under the License.

. /etc/parallelcluster/cfnconfig

LVM_VG_NAME="vg.01"
LVM_NAME="lv_ephemeral"
LVM_PATH="/dev/${LVM_VG_NAME}/${LVM_NAME}"
LVM_ACTIVE_STATE="a"
FS_TYPE="ext4"
MOUNT_OPTIONS="noatime,nodiratime"
# cfn_ephemeral_dir is set in the environment by cfnconfig sourcing
INPUT_MOUNTPOINT="${cfn_ephemeral_dir}"

function log {
  SCRIPT=$(basename "$0")
  MESSAGE="$1"
  echo "ParallelCluster - ${MESSAGE}"
}

function error_exit {
  log "[ERROR] $1"
  exit 1
}

function exit_noop {
  log "[INFO] $1"
  exit 0
}

function parameter_check {
  if [[ -z "${INPUT_MOUNTPOINT}" ]]; then
    exit_noop "Mount point not specified"
  fi
}

function set_imds_token {
  if [[ -z "${IMDS_TOKEN}" ]];then
    IMDS_TOKEN=$(curl --retry 3 --retry-delay 0 --fail -s -f -X PUT -H "X-aws-ec2-metadata-token-ttl-seconds: 900" http://169.254.169.254/latest/api/token)
    if [[ "$?" -gt 0 ]] || [[ -z "${IMDS_TOKEN}" ]]; then
      error_exit "Could not get IMDSv2 token. Instance Metadata might have been disabled or this is not an EC2 instance"
    fi
  fi
}

function get_metadata {
    QUERY=$1
    local IMDS_OUTPUT
    IMDS_OUTPUT=$(curl --retry 3 --retry-delay 0 --fail -s -q -H "X-aws-ec2-metadata-token:${IMDS_TOKEN}" -f "http://169.254.169.254/latest/${QUERY}")
    echo -n "${IMDS_OUTPUT}"
}

function print_block_device_mapping {
  echo 'block-device-mapping: '
  DEVICE_MAPPING_LIST=$(get_metadata meta-data/block-device-mapping/)
  if [[ -n "${DEVICE_MAPPING_LIST}" ]]; then
    for DEVICE_MAPPING in ${DEVICE_MAPPING_LIST}; do
      echo -e '\t' "${DEVICE_MAPPING}: $(get_metadata meta-data/block-device-mapping/"${DEVICE_MAPPING}")"
    done
  else
    echo "NOT AVAILABLE"
  fi
}

function check_instance_store {
  if ls /dev/nvme* >& /dev/null; then
    IS_NVME=1
    MAPPINGS=$(realpath --relative-to=/dev/ -P /dev/disk/by-id/nvme*Instance_Storage* | grep -v "*Instance_Storage*" | uniq)
  else
    IS_NVME=0
    set_imds_token
    MAPPINGS=$(print_block_device_mapping | grep ephemeral | awk '{print $2}' | sed 's/sd/xvd/')
  fi

  NUM_DEVICES=0
  for MAPPING in ${MAPPINGS}; do
    umount "/dev/${MAPPING}" &>/dev/null
    STAT_COMMAND="stat -t /dev/${MAPPING}"
    if ${STAT_COMMAND} &>/dev/null; then
      DEVICES+=("/dev/${MAPPING}")
      NUM_DEVICES=$((NUM_DEVICES + 1))
    fi
  done

  if [[ "${NUM_DEVICES}" -gt 0 ]]; then
    log "This instance type has (${NUM_DEVICES}) device(s) for instance store: (${DEVICES[*]})"
  else
    exit_noop "This instance type doesn't have instance store"
  fi

  if [[ "${IS_NVME}" -eq 0 ]]; then
    log "This instance store may suffer first-write penalty unless initialized: please have a look at https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/disk-performance.html"
    # Initialization can take long time, even hours
    # for DEVICE in "${DEVICES[@]}"; do
    #  dd if=/dev/zero of="${DEVICE}" bs=1M
    # done
  fi
}

function create_lvm {
  log "Creating LVM (${LVM_PATH})"
  pvcreate -y "${DEVICES[@]}"
  vgcreate -y "${LVM_VG_NAME}" "${DEVICES[@]}"
  LVM_CREATE_COMMAND="lvcreate -y -i ${NUM_DEVICES} -I 64 -l 100%FREE -n ${LVM_NAME} ${LVM_VG_NAME}"
  if ! ${LVM_CREATE_COMMAND}; then
    error_exit "Failed to create LVM"
  else
    log "LVM (${LVM_PATH}) created successfully"
  fi
}

function check_lvm_exist {
  LVM_EXIST_COMMAND="lvs ${LVM_PATH} --nosuffix --noheadings -q"

  if ! ${LVM_EXIST_COMMAND} &>/dev/null; then
    log "LVM (${LVM_PATH}) does not exist"
    create_lvm
  else
    log "LVM (${LVM_PATH}) already exists"
  fi
}

function activate_lvm {
  LVM_STATE=$(lvs "${LVM_PATH}" --nosuffix --noheadings -o lv_attr | xargs | cut -c5)
  log "Found LVM (${LVM_PATH}) in state (${LVM_STATE})"

  if [[ "${LVM_STATE}" != "${LVM_ACTIVE_STATE}" ]]; then
    log "Activating LVM (${LVM_PATH})"
    LVM_ACTIVATE_COMMAND="lvchange -ay ${LVM_PATH}"
    if ! ${LVM_ACTIVATE_COMMAND}; then
      error_exit "Failed to activate LVM"
    else
      log "LVM (${LVM_PATH}) activated successfully"
    fi
  fi
}

function format_lvm {
  LVM_FS_TYPE=$(lsblk "${LVM_PATH}" --noheadings -o FSTYPE | xargs)
  log "Found LVM (${LVM_PATH}) FS type (${LVM_FS_TYPE})"

  if [[ "${LVM_FS_TYPE}" != "${FS_TYPE}" ]]; then
    log "Formatting LVM (${LVM_PATH}) with FS type (${FS_TYPE})"
    LVM_FORMAT_COMMAND="mkfs -t ${FS_TYPE} ${LVM_PATH}"
    if ! ${LVM_FORMAT_COMMAND}; then
      error_exit "Failed to format LVM"
    else
      log "LVM (${LVM_PATH}) formatted successfully"
    fi
    sync
    sleep 1
  else
    log "LVM (${LVM_PATH}) already formatted with FS type (${LVM_FS_TYPE})"
  fi
}

function mount_lvm {
  LVM_MOUNTPOINT=$(lsblk "${LVM_PATH}" -o MOUNTPOINT --noheadings | xargs)

  if [[ -z ${LVM_MOUNTPOINT} ]]; then
    log "LVM (${LVM_PATH}) not mounted, mounting on (${INPUT_MOUNTPOINT})"
    # create mount
    mkdir -p "${INPUT_MOUNTPOINT}"
    LVM_MOUNT_COMMAND="mount -v -t ${FS_TYPE} -o ${MOUNT_OPTIONS} ${LVM_PATH} ${INPUT_MOUNTPOINT}"
    if ! ${LVM_MOUNT_COMMAND}; then
      error_exit "Failed to mount LVM"
    else
      log "LVM (${LVM_PATH}) mounted successfully"
    fi
    # set mount permission
    chmod 1777 "${INPUT_MOUNTPOINT}"
  else
    log "LVM (${LVM_PATH}) already mounted on (${LVM_MOUNTPOINT})"
  fi
}

function main {
  parameter_check
  check_instance_store
  check_lvm_exist
  activate_lvm
  format_lvm
  mount_lvm
}

main


#!/bin/sh
# Configure a specific Network Interface according to the OS
# The configuration involves 3 aspects:
# - Main configuration (IP address, protocol and gateway)
# - A specific routing table, so that all traffic coming to a network interface leaves the instance using the same
#   interface
# - A routing rule to make the OS use the specific routing table for this network interface

set -e

if
  [ -z "${DEVICE_NAME}" ] ||          # name of the device
  [ -z "${DEVICE_NUMBER}" ] ||        # number of the device
  [ -z "${GW_IP_ADDRESS}" ] ||        # gateway ip address
  [ -z "${DEVICE_IP_ADDRESS}" ] ||    # ip address to assign to the interface
  [ -z "${CIDR_PREFIX_LENGTH}" ] ||   # the prefix length of the device IP cidr block
  [ -z "${NETMASK}" ]                 # netmask to apply to device ip address
then
  echo 'One or more environment variables missing'
  exit 1
fi

ROUTE_TABLE="100${DEVICE_NUMBER}"

echo "Configuring ${DEVICE_NAME} with IP:${DEVICE_IP_ADDRESS} CIDR_PREFIX:${CIDR_PREFIX_LENGTH} NETMASK:${NETMASK} GW:${GW_IP_ADDRESS} ROUTING_TABLE:${ROUTE_TABLE}"

# config file
FILE="/etc/sysconfig/network-scripts/ifcfg-${DEVICE_NAME}"
if [ ! -f "$FILE" ]; then
/bin/cat <<EOF >${FILE}
DEVICE=${DEVICE_NAME}
TYPE=Ethernet
ONBOOT=yes
BOOTPROTO=none
IPADDR=${DEVICE_IP_ADDRESS}
PREFIX=${CIDR_PREFIX_LENGTH}
GATEWAY=${GW_IP_ADDRESS}
MTU="9001"
IPV4_FAILURE_FATAL=yes
NAME="System ${DEVICE_NAME}"
EOF
fi

# route file
FILE="/etc/sysconfig/network-scripts/route-${DEVICE_NAME}"
if [ ! -f "$FILE" ]; then
/bin/cat <<EOF >${FILE}
default via ${GW_IP_ADDRESS} dev ${DEVICE_NAME} table ${ROUTE_TABLE}
default via ${GW_IP_ADDRESS} dev ${DEVICE_NAME} metric ${ROUTE_TABLE}
${DEVICE_IP_ADDRESS} dev ${DEVICE_NAME} table ${ROUTE_TABLE}
EOF
fi

# rule file
FILE="/etc/sysconfig/network-scripts/rule-${DEVICE_NAME}"
if [ ! -f "$FILE" ]; then
/bin/cat <<EOF >${FILE}
from ${DEVICE_IP_ADDRESS} lookup ${ROUTE_TABLE}
EOF
fi


#!/bin/bash
set -e
#
# Manage the access to IMDS
#
# --allow <user1,...,userN>   List of users to allow access to IMDS
# --deny  <user1,...,userN>   List of users to deny access to IMDS
# --unset <user1,...,userN>   Remove iptables rules related to IMDS for the given list of users
# --flush                     Restore default IMDS access
# --help                      Print this help message

function error() {
  >&2 echo "[ERROR] $1"
  exit 1
}

function info() {
  echo "[INFO] $1"
}

function help() {
  local -- cmd=$(basename "$0")
  cat <<EOF

  Usage: ${cmd} [OPTION]...

  Manage the access to IMDS

  --allow <user1,...,userN>   Allow IMDS access to the given list of users
  --deny  <user1,...,userN>   Deny IMDS access to the given list of users
  --unset <user1,...,userN>   Remove iptables rules related to IMDS for the given list of users
  --flush                     Restore default IMDS access
  --help                      Print this help message
EOF
}

function iptables_delete() {
  local iptables_command=$1
  local chain=$2
  local destination=$3
  local jump=$4
  local user=$5

  # Build iptables delete command
  if [[ -z $user ]]; then
    rule_args="$chain --destination $destination -j $jump"
  else
    rule_args="$chain --destination $destination -j $jump -m owner --uid-owner $user"
  fi

  local iptables_delete_command="$iptables_command -D $rule_args"

  # Remove rules
  local should_remove=true
  while $should_remove; do
    eval $iptables_delete_command 1>/dev/null 2>/dev/null || should_remove=false
  done
}

function iptables_add() {
  local iptables_command=$1
  local chain=$2
  local destination=$3
  local jump=$4
  local user=$5

  # Remove duplicate rules
  iptables_delete $iptables_command $chain $destination $jump $user

  # Remove opposite rules
  if [[ $jump == "ACCEPT" ]]; then
    iptables_delete $iptables_command $destination "REJECT" $user
  elif [[ $jump == "REJECT" ]]; then
    iptables_delete $iptables_command $destination "ACCEPT" $user
  fi

  # Build iptables add command
  if [[ -z $user ]]; then
    rule_args="$chain --destination $destination -j $jump"
  else
    rule_args="$chain --destination $destination -j $jump -m owner --uid-owner $user"
  fi

  local iptables_add_command="$iptables_command -A $rule_args"

  # Add rule
  eval $iptables_add_command
  info "Rule in chain $chain: $destination $jump $user"
}

function setup_chain() {
  local iptables_command=$1
  local chain=$2
  local source_chain=$3
  local destination=$4

  $iptables_command --new $chain 2>/dev/null && info "ParallelCluster chain created: $chain" \
  || info "ParallelCluster chain exists: $chain"

  iptables_add $iptables_command $source_chain $destination $chain
}

main() {
  # Constants
  PARALLELCLUSTER_CHAIN="PARALLELCLUSTER_IMDS"
  OUTPUT_CHAIN="OUTPUT"
  IMDS_IP="169.254.169.254"
  IMDS_IP6="fd00:ec2::254"
  IPTABLE_CMD="iptables"
  IPTABLE_CMD_V6="ip6tables"

  # Parse options
  while [ $# -gt 0 ] ; do
      case "$1" in
          --allow)    allow_users="$2"; shift;;
          --deny)     deny_users="$2"; shift;;
          --unset)    unset_users="$2"; shift;;
          --flush)    flush="true";;
          --help)     help; exit 0;;
          *)          help; error "Unrecognized option '$1'";;
      esac
      shift
  done

  IFS=","
  for ip_version_address in $IPTABLE_CMD,$IMDS_IP $IPTABLE_CMD_V6,$IMDS_IP6
  do
    set -- $ip_version_address
    iptables_command=$1
    ip=$2

    # Check required commands
    command -v $iptables_command >/dev/null || error "Cannot find required command: $iptables_command"

    # Check arguments and options
    if [[ -z $allow_users && -z $deny_users && -z $unset_users && -z $flush ]]; then
      error "Missing at least one mandatory option: '--allow', '--deny', '--unset', '--flush'"
    fi

    # Setup ParallelCluster chain
    setup_chain $iptables_command $PARALLELCLUSTER_CHAIN $OUTPUT_CHAIN $ip

    # Flush ParallelCluster chain, if required
    if [[ $flush == "true" ]]; then
      $iptables_command --flush $PARALLELCLUSTER_CHAIN
      info "ParallelCluster chain flushed"
      exit 0
    fi

    # Delete rule: ACCEPT/REJECT user, for every user to unset
    for user in $unset_users; do
      info "Deleting rules related to IMDS access for user: $user"
      iptables_delete $iptables_command $PARALLELCLUSTER_CHAIN $ip "ACCEPT" $user
      iptables_delete $iptables_command $PARALLELCLUSTER_CHAIN $ip "REJECT" $user
    done

    # Add rule: ACCEPT user, for every allowed user
    for user in $allow_users; do
      info "Allowing IMDS access for user: $user"
      iptables_add $iptables_command $PARALLELCLUSTER_CHAIN $ip "ACCEPT" $user
    done

    # Add rule: REJECT user, for every denied user
    for user in $deny_users; do
      info "Denying IMDS access for user: $user"
      iptables_add $iptables_command $PARALLELCLUSTER_CHAIN $ip "REJECT" $user
    done

    # Add rule: REJECT not allowed users
    info "Denying IMDS access for not allowed users"
    iptables_add $iptables_command $PARALLELCLUSTER_CHAIN $ip "REJECT"
  done
}

main "$@"


#!/bin/sh
# Configure a specific Network Interface according to the OS
# The configuration involves 3 aspects:
# - Main configuration (IP address, protocol and gateway)
# - A specific routing table, so that all traffic coming to a network interface leaves the instance using the same
#   interface
# - A routing rule to make the OS use the specific routing table for this network interface

# RedHat 8 official documentation:
# https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/configuring_and_managing_networking/configuring-policy-based-routing-to-define-alternative-routes_configuring-and-managing-networking

set -e

if
  [ -z "${DEVICE_NAME}" ] ||          # name of the device
  [ -z "${DEVICE_NUMBER}" ] ||        # number of the device
  [ -z "${GW_IP_ADDRESS}" ] ||        # gateway ip address
  [ -z "${DEVICE_IP_ADDRESS}" ] ||    # ip address to assign to the interface
  [ -z "${CIDR_PREFIX_LENGTH}" ]      # the prefix length of the device IP cidr block
then
  echo 'One or more environment variables missing'
  exit 1
fi

con_name="System ${DEVICE_NAME}"
route_table="100${DEVICE_NUMBER}"
priority="100${DEVICE_NUMBER}"
metric="100${DEVICE_NUMBER}"

# Rename connection
original_con_name=`nmcli -t -f GENERAL.CONNECTION device show ${DEVICE_NAME} | cut -f2 -d':'`
sudo nmcli connection modify "${original_con_name}" con-name "${con_name}" ifname ${DEVICE_NAME}

configured_ip=`nmcli -t -f IP4.ADDRESS device show ${DEVICE_NAME} | cut -f2 -d':'`
if [ -z "${configured_ip}" ]; then
  # Setup connection method to "manual", configure ip address and gateway, only if not already configured.
  sudo nmcli connection modify "${con_name}" ipv4.method manual ipv4.addresses ${DEVICE_IP_ADDRESS}/${CIDR_PREFIX_LENGTH} ipv4.gateway ${GW_IP_ADDRESS}
fi

# Setup routes
# This command uses the ipv4.routes parameter to add a static route to the routing table with ID ${route_table}.
# This static route for 0.0.0.0/0 uses the IP of the gateway as next hop.
sudo nmcli connection modify "${con_name}" ipv4.routes "0.0.0.0/0 ${GW_IP_ADDRESS} ${metric} table=${route_table}"

# Setup routing rules
# The command uses the ipv4.routing-rules parameter to add a routing rule with priority ${priority} that routes
# traffic from ${DEVICE_IP_ADDRESS} to table ${route_table}. Low values have a high priority.
# The syntax in the ipv4.routing-rules parameter is the same as in an "ip rule add" command,
# except that ipv4.routing-rules always requires specifying a priority.
sudo nmcli connection modify "${con_name}" ipv4.routing-rules "priority ${priority} from ${DEVICE_IP_ADDRESS} table ${route_table}"

# Reapply previous connection modification.
sudo nmcli device reapply ${DEVICE_NAME}


#!/bin/sh
# Configure a specific Network Interface according to the OS
# The configuration involves 3 aspects:
# - Main configuration (IP address, protocol and gateway)
# - A specific routing table, so that all traffic coming to a network interface leaves the instance using the same
#   interface
# - A routing rule to make the OS use the specific routing table for this network interface

set -e

if
  [ -z "${DEVICE_NAME}" ] ||          # name of the device
  [ -z "${DEVICE_NUMBER}" ] ||        # number of the device
  [ -z "${GW_IP_ADDRESS}" ] ||        # gateway ip address
  [ -z "${DEVICE_IP_ADDRESS}" ] ||    # ip address to assign to the interface
  [ -z "${CIDR_PREFIX_LENGTH}" ] ||   # the prefix length of the device IP cidr block
  [ -z "${NETMASK}" ] ||              # netmask to apply to device ip address
  [ -z "${CIDR_BLOCK}" ]              # (full) subnet cidr block
then
  echo 'One or more environment variables missing'
  exit 1
fi

STATIC_IP_CONFIG=$(cat<<END
      addresses:
       - ${DEVICE_IP_ADDRESS}/${CIDR_PREFIX_LENGTH}
      dhcp4: no
END
)

# NOTE: In Ubuntu 20.04 all network interfaces are already configured in 50-cloud-init.yaml with dhcp4 enabled.
# However, the specific configuration files created below (in the same way of Ubuntu 18) will override these initial
# settings, as can be verified with the command `netplan get`
grep "${DEVICE_NAME}:" /etc/netplan/50-cloud-init.yaml 1>/dev/null && STATIC_IP_CONFIG=""
if [ "${STATIC_IP_CONFIG}" = "" ]
  then
    echo "Device ${DEVICE_NAME} is dhcp managed in current platform"
  else
    echo "Device ${DEVICE_NAME} IP address will be set to ${DEVICE_IP_ADDRESS}"
fi

FILE="/etc/netplan/${DEVICE_NAME}.yaml"
ROUTE_TABLE="100${DEVICE_NUMBER}"

echo "Configuring ${DEVICE_NAME} with IP:${DEVICE_IP_ADDRESS} CIDR_PREFIX:${CIDR_PREFIX_LENGTH} NETMASK:${NETMASK} GW:${GW_IP_ADDRESS} ROUTING_TABLE:${ROUTE_TABLE}"

/bin/cat <<EOF >${FILE}
network:
  version: 2
  renderer: networkd
  ethernets:
    ${DEVICE_NAME}:
$STATIC_IP_CONFIG
      mtu: '9001'
      routes:
       - to: 0.0.0.0/0
         via: ${GW_IP_ADDRESS} # Default gateway
         table: ${ROUTE_TABLE}
       - to: 0.0.0.0/0
         via: ${GW_IP_ADDRESS} # Default gateway
         metric: ${ROUTE_TABLE}
       - to: ${DEVICE_IP_ADDRESS}
         via: 0.0.0.0
         scope: link
         table: ${ROUTE_TABLE}
      routing-policy:
        - from: ${DEVICE_IP_ADDRESS}
          table: ${ROUTE_TABLE}
EOF

# Hook to delete automatic routes to subnet automatically created from kernel.
# This is needed in Ubuntu 18 because they are generated in the reverse order in which the network interfaces are
# attached and this prevents the primary Network Interface from being selected to communicate in the subnet.
# In Ubuntu 20 this hook is needed as well for a slightly different reason. Automatic routes here are
# already present due to cloud-init initialization and we need to remove them to make sure that the primary Network
# Interface is selected when opening connections to the subnet
FILE="/etc/networkd-dispatcher/routable.d/cleanup-routes.sh"

if [ ! -f "$FILE" ]; then
/bin/cat <<EOF >${FILE}
#!/bin/sh

logger -t parallelcluster "Removing Automatic route for Interface: \${IFACE}"
ip route del ${CIDR_BLOCK} dev \${IFACE}
logger -t parallelcluster "Automatic route removed for Interface: \${IFACE}"
EOF

# The hook script file must be executable and owned by root
chmod 755 ${FILE}
fi


#!/bin/bash

echo "**** post_create"

[[ "${KITCHEN_DRIVER}" = "ec2" ]] || exit 0

RESOURCE_NAME=ebs_mount-vol_array

echo "** Creating EBS volume..."

KITCHEN_EBS_VOLUME_ID=$(aws ec2 create-volume \
        --region "${KITCHEN_AWS_REGION}" \
        --availability-zone "${KITCHEN_AWS_REGION}${KITCHEN_AVAILABILITY_ZONE}" \
        --size 1 \
        --tag-specifications "ResourceType=volume,Tags=[{Key=Kitchen, Value=true},{Key=Platform, Value=${KITCHEN_PLATFORM_NAME}},{Key=ResourceName, Value=${RESOURCE_NAME}}]" \
        --query "VolumeId" --output text)

echo "** EBS volume created: ${KITCHEN_EBS_VOLUME_ID}"

sed -i.bak "s#'${RESOURCE_NAME}/${KITCHEN_PLATFORM_NAME}' => .*,#'${RESOURCE_NAME}/${KITCHEN_PLATFORM_NAME}' => %w(${KITCHEN_EBS_VOLUME_ID}),#" "${KITCHEN_ROOT_DIR}/test/environments/kitchen.rb"


#!/bin/bash

echo "**** pre_destroy"

[[ "${KITCHEN_DRIVER}" = "ec2" ]] || exit 0
[ -n "${KITCHEN_INSTANCE_HOSTNAME}" ] || exit 0

RESOURCE_NAME=ebs_mount-vol_array

KITCHEN_EBS_VOLUME_ID=$(cat "${KITCHEN_ROOT_DIR}/test/environments/kitchen.rb" | sed -n -e "s/.*'${RESOURCE_NAME}\/${KITCHEN_PLATFORM_NAME}' => %w(\(.*\)),/\1/p")
[ -z "${KITCHEN_EBS_VOLUME_ID}" ] && echo "Volume not available. Skipping pre_destroy hook." && exit 0

echo "** Deleting EBS volume ${KITCHEN_EBS_VOLUME_ID}"

aws ec2 detach-volume --volume-id "${KITCHEN_EBS_VOLUME_ID}" --region "${KITCHEN_AWS_REGION}"
echo "** EBS volume ${KITCHEN_EBS_VOLUME_ID} detached"

aws ec2 wait volume-available --volume-ids ${KITCHEN_EBS_VOLUME_ID} --region "${KITCHEN_AWS_REGION}"

aws ec2 delete-volume --volume-id "${KITCHEN_EBS_VOLUME_ID}" --region "${KITCHEN_AWS_REGION}"
echo "** EBS volumes ${KITCHEN_EBS_VOLUME_ID} deleted"


#!/bin/bash

echo "**** post_create"

[[ "${KITCHEN_DRIVER}" = "ec2" ]] || exit 0

RESOURCE_NAME=ebs_unmount-vol_array

echo "** Creating EBS volume..."

KITCHEN_EBS_VOLUME_ID=$(aws ec2 create-volume \
        --region "${KITCHEN_AWS_REGION}" \
        --availability-zone "${KITCHEN_AWS_REGION}${KITCHEN_AVAILABILITY_ZONE}" \
        --size 1 \
        --tag-specifications "ResourceType=volume,Tags=[{Key=Kitchen, Value=true},{Key=Platform, Value=${KITCHEN_PLATFORM_NAME}},{Key=ResourceName, Value=${RESOURCE_NAME}}]" \
        --query "VolumeId" --output text)

echo "** EBS volume created: ${KITCHEN_EBS_VOLUME_ID}"

sed -i.bak "s#'${RESOURCE_NAME}/${KITCHEN_PLATFORM_NAME}' => .*,#'${RESOURCE_NAME}/${KITCHEN_PLATFORM_NAME}' => %w(${KITCHEN_EBS_VOLUME_ID}),#" "${KITCHEN_ROOT_DIR}/test/environments/kitchen.rb"


#!/bin/bash

echo "**** pre_destroy"

[[ "${KITCHEN_DRIVER}" = "ec2" ]] || exit 0
[ -n "${KITCHEN_INSTANCE_HOSTNAME}" ] || exit 0

RESOURCE_NAME=ebs_unmount-vol_array

KITCHEN_EBS_VOLUME_ID=$(cat "${KITCHEN_ROOT_DIR}/test/environments/kitchen.rb" | sed -n -e "s/.*'${RESOURCE_NAME}\/${KITCHEN_PLATFORM_NAME}' => %w(\(.*\)),/\1/p")
[ -z "${KITCHEN_EBS_VOLUME_ID}" ] && echo "Volume not available. Skipping pre_destroy hook." && exit 0

echo "** Deleting EBS volume ${KITCHEN_EBS_VOLUME_ID}"

aws ec2 detach-volume --volume-id "${KITCHEN_EBS_VOLUME_ID}" --region "${KITCHEN_AWS_REGION}"
echo "** EBS volume ${KITCHEN_EBS_VOLUME_ID} detached"

aws ec2 wait volume-available --volume-ids ${KITCHEN_EBS_VOLUME_ID} --region "${KITCHEN_AWS_REGION}"

aws ec2 delete-volume --volume-id "${KITCHEN_EBS_VOLUME_ID}" --region "${KITCHEN_AWS_REGION}"
echo "** EBS volumes ${KITCHEN_EBS_VOLUME_ID} deleted"


#!/bin/bash

echo "**** post_create"

[[ "${KITCHEN_DRIVER}" = "ec2" ]] || return

echo "** Creating ENI..."

KITCHEN_ENI_ID=$(aws ec2 create-network-interface --region "${KITCHEN_AWS_REGION}" \
    --description "Kitchen tests ENI for ${KITCHEN_SUITE_NAME} on ${KITCHEN_EC2_INSTANCE_ID}" \
    --subnet-id "${KITCHEN_SUBNET_ID}" \
    --groups "${KITCHEN_SECURITY_GROUP_ID}" \
    --tag-specifications "ResourceType=network-interface,Tags=[{Key=Kitchen, Value=true}]" \
    --query "NetworkInterface.NetworkInterfaceId" --output text)

echo "** ENI created: ${KITCHEN_ENI_ID}"

aws ec2 attach-network-interface --region "${KITCHEN_AWS_REGION}" \
    --device-index 1 --instance-id "${KITCHEN_EC2_INSTANCE_ID}" \
    --network-interface-id "${KITCHEN_ENI_ID}" \
    --network-card-index 1


#!/bin/bash

echo "**** pre_destroy"

[[ "${KITCHEN_DRIVER}" = "ec2" ]] || return
[ -z "${KITCHEN_EC2_INSTANCE_ID}" ] && echo "EC2 instance not available. Skipping pre_destroy hook." && exit 0

read KITCHEN_ENI_ID KITCHEN_ATTACHMENT_ID <<< "$(aws ec2 describe-instances --instance-ids "${KITCHEN_EC2_INSTANCE_ID}" \
    --query "Reservations[0].Instances[0].NetworkInterfaces[?Attachment.DeviceIndex==\`1\`].[NetworkInterfaceId, Attachment.AttachmentId]" \
    --region "${KITCHEN_AWS_REGION}" --output text)"

if [ -n "${KITCHEN_ATTACHMENT_ID}" ]; then
  echo "** Detaching ENI: ${KITCHEN_ATTACHMENT_ID}"
  aws ec2 detach-network-interface --attachment-id "${KITCHEN_ATTACHMENT_ID}" --region "${KITCHEN_AWS_REGION}"
fi

if [ -n "${KITCHEN_ENI_ID}" ]; then
  echo "** Waiting for ENI to be detached: ${KITCHEN_ENI_ID}"
  aws ec2 wait network-interface-available --network-interface-ids "${KITCHEN_ENI_ID}" --region "${KITCHEN_AWS_REGION}"

  echo "** Deleting ENI: ${KITCHEN_ENI_ID}"
  aws ec2 delete-network-interface --network-interface-id "${KITCHEN_ENI_ID}" --region "${KITCHEN_AWS_REGION}"
fi


#!/bin/bash

echo "**** post_create"

[[ "${KITCHEN_DRIVER}" = "ec2" ]] || return

RESOURCE_NAME=raid_mount-raid_vol_array

echo "** Creating 2 EBS volumes..."

KITCHEN_EBS_VOLUME_ID1=$(aws ec2 create-volume \
        --region "${KITCHEN_AWS_REGION}" \
        --availability-zone "${KITCHEN_AWS_REGION}${KITCHEN_AVAILABILITY_ZONE}" \
        --size 1 \
        --tag-specifications "ResourceType=volume,Tags=[{Key=Kitchen, Value=true},{Key=Platform, Value=${KITCHEN_PLATFORM_NAME}},{Key=ResourceName, Value=${RESOURCE_NAME}}]" \
        --query "VolumeId" --output text)

echo "** EBS volume 1 created: ${KITCHEN_EBS_VOLUME_ID1}"

KITCHEN_EBS_VOLUME_ID2=$(aws ec2 create-volume \
        --region "${KITCHEN_AWS_REGION}" \
        --availability-zone "${KITCHEN_AWS_REGION}${KITCHEN_AVAILABILITY_ZONE}" \
        --size 1 \
        --tag-specifications "ResourceType=volume,Tags=[{Key=Kitchen, Value=true},{Key=Platform, Value=${KITCHEN_PLATFORM_NAME}},{Key=ResourceName, Value=${RESOURCE_NAME}}]" \
        --query "VolumeId" --output text)

echo "** EBS volume 2 created: ${KITCHEN_EBS_VOLUME_ID2}"

sed -i.bak "s#'${RESOURCE_NAME}/${KITCHEN_PLATFORM_NAME}' => .*,#'${RESOURCE_NAME}/${KITCHEN_PLATFORM_NAME}' => %w(${KITCHEN_EBS_VOLUME_ID1} ${KITCHEN_EBS_VOLUME_ID2}),#" "${KITCHEN_ROOT_DIR}/test/environments/kitchen.rb"


#!/bin/bash

echo "**** pre_destroy"

[[ "${KITCHEN_DRIVER}" = "ec2" ]] || exit 0
[ -n "${KITCHEN_INSTANCE_HOSTNAME}" ] || exit 0

RESOURCE_NAME=raid_mount-raid_vol_array

KITCHEN_EBS_VOLUME_ID1=$(cat "${KITCHEN_ROOT_DIR}/test/environments/kitchen.rb" | sed -n -e "s/.*'${RESOURCE_NAME}\/${KITCHEN_PLATFORM_NAME}' => %w(\(.*\) \(.*\)),/\1/p")
KITCHEN_EBS_VOLUME_ID2=$(cat "${KITCHEN_ROOT_DIR}/test/environments/kitchen.rb" | sed -n -e "s/.*'${RESOURCE_NAME}\/${KITCHEN_PLATFORM_NAME}' => %w(\(.*\) \(.*\)),/\2/p")
[ -z "${KITCHEN_EBS_VOLUME_ID1}" ] && [ -z "${KITCHEN_EBS_VOLUME_ID2}" ] && echo "Volumes not available. Skipping pre_destroy hook." && exit 0

echo "** Deleting EBS volumes ${KITCHEN_EBS_VOLUME_ID1} ${KITCHEN_EBS_VOLUME_ID2}"

aws ec2 detach-volume --volume-id "${KITCHEN_EBS_VOLUME_ID1}" --region "${KITCHEN_AWS_REGION}"
echo "** EBS volume ${KITCHEN_EBS_VOLUME_ID1} detached"
aws ec2 detach-volume --volume-id "${KITCHEN_EBS_VOLUME_ID2}" --region "${KITCHEN_AWS_REGION}"
echo "** EBS volume ${KITCHEN_EBS_VOLUME_ID2} detached"

aws ec2 wait volume-available --volume-ids ${KITCHEN_EBS_VOLUME_ID1} ${KITCHEN_EBS_VOLUME_ID2} --region "${KITCHEN_AWS_REGION}"

aws ec2 delete-volume --volume-id "${KITCHEN_EBS_VOLUME_ID1}" --region "${KITCHEN_AWS_REGION}"
aws ec2 delete-volume --volume-id "${KITCHEN_EBS_VOLUME_ID2}" --region "${KITCHEN_AWS_REGION}"
echo "** EBS volumes ${KITCHEN_EBS_VOLUME_ID1} and ${KITCHEN_EBS_VOLUME_ID2} deleted"


#!/bin/bash

echo "**** post_create"

[[ "${KITCHEN_DRIVER}" = "ec2" ]] || return

RESOURCE_NAME=raid_unmount-raid_vol_array

echo "** Creating 2 EBS volumes..."

KITCHEN_EBS_VOLUME_ID1=$(aws ec2 create-volume \
        --region "${KITCHEN_AWS_REGION}" \
        --availability-zone "${KITCHEN_AWS_REGION}${KITCHEN_AVAILABILITY_ZONE}" \
        --size 1 \
        --tag-specifications "ResourceType=volume,Tags=[{Key=Kitchen, Value=true},{Key=Platform, Value=${KITCHEN_PLATFORM_NAME}},{Key=ResourceName, Value=${RESOURCE_NAME}}]" \
        --query "VolumeId" --output text)

echo "** EBS volume 1 created: ${KITCHEN_EBS_VOLUME_ID1}"

KITCHEN_EBS_VOLUME_ID2=$(aws ec2 create-volume \
        --region "${KITCHEN_AWS_REGION}" \
        --availability-zone "${KITCHEN_AWS_REGION}${KITCHEN_AVAILABILITY_ZONE}" \
        --size 1 \
        --tag-specifications "ResourceType=volume,Tags=[{Key=Kitchen, Value=true},{Key=Platform, Value=${KITCHEN_PLATFORM_NAME}},{Key=ResourceName, Value=${RESOURCE_NAME}}]" \
        --query "VolumeId" --output text)

echo "** EBS volume 2 created: ${KITCHEN_EBS_VOLUME_ID2}"

sed -i.bak "s#'${RESOURCE_NAME}/${KITCHEN_PLATFORM_NAME}' => .*,#'${RESOURCE_NAME}/${KITCHEN_PLATFORM_NAME}' => %w(${KITCHEN_EBS_VOLUME_ID1} ${KITCHEN_EBS_VOLUME_ID2}),#" "${KITCHEN_ROOT_DIR}/test/environments/kitchen.rb"


#!/bin/bash

echo "**** pre_destroy"

[[ "${KITCHEN_DRIVER}" = "ec2" ]] || exit 0
[ -n "${KITCHEN_INSTANCE_HOSTNAME}" ] || exit 0

RESOURCE_NAME=raid_unmount-raid_vol_array

KITCHEN_EBS_VOLUME_ID1=$(cat "${KITCHEN_ROOT_DIR}/test/environments/kitchen.rb" | sed -n -e "s/.*'${RESOURCE_NAME}\/${KITCHEN_PLATFORM_NAME}' => %w(\(.*\) \(.*\)),/\1/p")
KITCHEN_EBS_VOLUME_ID2=$(cat "${KITCHEN_ROOT_DIR}/test/environments/kitchen.rb" | sed -n -e "s/.*'${RESOURCE_NAME}\/${KITCHEN_PLATFORM_NAME}' => %w(\(.*\) \(.*\)),/\2/p")
[ -z "${KITCHEN_EBS_VOLUME_ID1}" ] && [ -z "${KITCHEN_EBS_VOLUME_ID2}" ] && echo "Volumes not available. Skipping pre_destroy hook." && exit 0

echo "** Deleting EBS volumes ${KITCHEN_EBS_VOLUME_ID1} ${KITCHEN_EBS_VOLUME_ID2}"

aws ec2 detach-volume --volume-id "${KITCHEN_EBS_VOLUME_ID1}" --region "${KITCHEN_AWS_REGION}"
echo "** EBS volume ${KITCHEN_EBS_VOLUME_ID1} detached"
aws ec2 detach-volume --volume-id "${KITCHEN_EBS_VOLUME_ID2}" --region "${KITCHEN_AWS_REGION}"
echo "** EBS volume ${KITCHEN_EBS_VOLUME_ID2} detached"

aws ec2 wait volume-available --volume-ids ${KITCHEN_EBS_VOLUME_ID1} ${KITCHEN_EBS_VOLUME_ID2} --region "${KITCHEN_AWS_REGION}"

aws ec2 delete-volume --volume-id "${KITCHEN_EBS_VOLUME_ID1}" --region "${KITCHEN_AWS_REGION}"
aws ec2 delete-volume --volume-id "${KITCHEN_EBS_VOLUME_ID2}" --region "${KITCHEN_AWS_REGION}"
echo "** EBS volumes ${KITCHEN_EBS_VOLUME_ID1} and ${KITCHEN_EBS_VOLUME_ID2} deleted"


#!/bin/bash

# clean up cloud init artifacts https://cloudinit.readthedocs.io/en/latest/topics/cli.html#clean
cloud-init clean -s

rm -rf /var/tmp/* /tmp/*
rm -rf /etc/ssh/ssh_host_*
rm -f /etc/udev/rules.d/70-persistent-net.rules
grep -l "Created by cloud-init on instance boot automatically" /etc/sysconfig/network-scripts/ifcfg-* | xargs rm -f
rm -rf /var/crash/*

# https://bugs.centos.org/view.php?id=13836#c33128
source /etc/os-release
if [ "${ID}${VERSION_ID}" == "centos7" ]; then
    rm -f /etc/sysconfig/network-scripts/ifcfg-eth0
fi

find /var/log -type f -exec /bin/rm -v {} \;
touch /var/log/lastlog


#!/usr/bin/env bash
#
# Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# A copy of the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "LICENSE.txt" file accompanying this file.
# This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied.
# See the License for the specific language governing permissions and limitations under the License.

# Generate a 5 year self-signed certificate and a new RSA private key and copy in the given path.
# The command must be executed as root.

# Usage:
# ./generate_certificate.sh \
#     "/etc/parallelcluster/ext-auth-certificate.pem" \
#     "/etc/parallelcluster/ext-auth-private-key.pem" \
#     dcv-ext-auth-user \
#     dcv-ext-auth-user-group

_check_param() {
    paramValue="$1"
    errorMessage="$2"
    if [[ -z "${paramValue}" ]]; then
        >&2 echo "${errorMessage}"
        exit 1
    fi
}

main() {
    certificatePath="$1"
    privateKeyPath="$2"
    user="$3"
    group="$4"

    _check_param "${certificatePath}" "Certificate file path required"
    _check_param "${privateKeyPath}" "Private Key file path required"
    _check_param "${user}" "User required"
    _check_param "${group}" "Group required"

    # Generate a new certificate and a new RSA private key
    openssl req -newkey rsa:2048 -sha256 -x509 -days 1825 -subj "/CN=localhost" -out "${certificatePath}" -nodes -keyout "${privateKeyPath}"
    chmod 440 "${certificatePath}" "${privateKeyPath}"
    chown "${user}":"${group}" "${certificatePath}" "${privateKeyPath}"
}

main "$@"


#!/usr/bin/env bash
#
# Cookbook Name:: aws-parallelcluster
#
# Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the
# License. A copy of the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
# OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
# limitations under the License.

# This script must be executed by the user who want to connect to DCV to obtain a Session Token.
#
# It performs the following steps:
# 1. Asks the ParallelCluster DCV External Authenticator for a Request Token and the Access File name
# 2. Creates the "access file" in the AUTHORIZATION_FILE_DIR folder (to confirm user identity)
# 3. Asks the ParallelCluster DCV External Authenticator for a SessionToken (the real token to access to the DCV session)
# 4. Returns DCV Session Id, DCV Server Port and the Session Token

# Requirements:
# jq, curl, awk, xargs, shuf, cat, grep, systemctl

# Usage:
# The script requires the ParallelCluster shared folder as input parameter
# The ParallelCluster shared folder will be used as session storage folder for DCV.
# It enables the clients to share files while connected to the session.
# ./pcluster_dcv_connect.sh "/shared"

# Returns the sessionid, the port and the tokenid (256 character long).
# Example: mysession 8443 adfsaklcxzvsadkhfgsdkhjfag-__bafbdajshsdjfh

AUTHORIZATION_FILE_DIR="/var/spool/parallelcluster/pcluster_dcv_authenticator"
DCV_SESSION_FOLDER="${HOME}/.parallelcluster/dcv"
LOG_FILE_PATH="/var/log/parallelcluster/pcluster_dcv_connect.log"
LOG_FILE_MAX_SIZE=5242880 # 5MB

_fail() {
  message=$1
  >&2 echo "ERROR: ${message}"
  exit 1
}

_validate_json() {
  json_param=$1
  message=$2

  if [[ -z "${json_param}" ]]; then
      _fail "${message}. Empty JSON."
  fi
  if ! $(jq -e . >/dev/null 2>&1 <<<"${json_param}"); then
      _fail "${message}. Wrong JSON."
  fi
}

_atomic_touch() {
    file_path="$1"

    _umask=$(umask)
    umask 0022 && touch "${file_path}"
    umask ${_umask}

    if [[ ! -f "${file_path}" ]]; then
        _fail "Unable to create the file in the $(dirname "${file_path}") folder. Exiting.."
    fi
}

_log() {
    text=$1

    [[ -f "${LOG_FILE_PATH}" ]] || _atomic_touch "${LOG_FILE_PATH}"

    # create a backup of the file if the size exceeds the LOG_FILE_MAX_SIZE
    file_size=$(du -b ${LOG_FILE_PATH} | awk '{print $1}')
    if [[ ${file_size} -gt ${LOG_FILE_MAX_SIZE} ]]; then
        mv "${LOG_FILE_PATH}" "${LOG_FILE_PATH}.1"
    fi

    # append log
    log_time=$(date "+%Y-%m-%d %H:%M:%S")
    echo "[${log_time}]: ${text}" >> "${LOG_FILE_PATH}"
}

_create_dcv_session() {
    dcv_session_file="$1"
    shared_folder_path="$2"

    _log "Creating a new NICE DCV session..."
    # Generate a random session id
    sessionid=$(shuf -zer -n20  {A..Z} {a..z} {0..9})
    echo "${sessionid}" > "${dcv_session_file}"
    dcv create-session --type virtual --storage-root "${shared_folder_path}" "${sessionid}"
    _log "NICE DCV session created successfully."

    echo "${sessionid}"
}

main() {
    _log "--- Initializing NICE DCV authentication... ---"

    if [[ -z "$1" ]]; then
        _fail "The script requires the shared folder as input parameter."
    fi

    shared_folder_path="$1"
    user=$(whoami)
    os=$(< /etc/chef/dna.json jq -r .cluster.base_os)
    _log "Input parameters: user: ${user}, OS: ${os}, shared_folder_path: ${shared_folder_path}."

    if ! [[ "${os}" =~ ^(alinux2|ubuntu2004|centos[7-8]|rhel8)$ ]]; then
        _fail "OS not supported."
    fi

    if ! systemctl is-active --quiet dcvserver; then
        _fail "NICE DCV service is not active on the given instance."
    fi

    # Create a session with session storage enabled.
    mkdir -p "${DCV_SESSION_FOLDER}"
    dcv_session_file="${DCV_SESSION_FOLDER}/dcv_session"
    if [[ ! -e ${dcv_session_file} ]]; then
        sessionid=$(_create_dcv_session "${dcv_session_file}" "${shared_folder_path}")
    else
        sessionid=$(cat "${dcv_session_file}")
        _log "Reusing existing NICE DCV session for the current user."

        # number of session can either be 0 or 1
        number_of_sessions=$(dcv list-sessions |& grep "${user}" | grep -c "${sessionid}")
        if (( number_of_sessions == 0 )); then
            # There is no running session (e.g. the system has been rebooted)
            _log "There are no running sessions for the current user, creating a new one.."
            sessionid=$(_create_dcv_session "${dcv_session_file}" "${shared_folder_path}")
        fi
    fi

    # xargs to remove eventual whitespaces
    dcv_server_port=$(grep web-port= /etc/dcv/dcv.conf| awk -F'=' '{ print $2 }' | xargs)
    ext_auth_port=$((dcv_server_port + 1))

    # Retrieve Request Token and Access File name
    _log "Retrieving Request Token and Access File name.."
    user_token_request=$(curl --retry 3 --max-time 5 -s -k -X GET -G "https://localhost:${ext_auth_port}" -d action=requestToken -d authUser="${user}" -d sessionID="${sessionid}")
    _validate_json "${user_token_request}" "Unable to obtain the Request Token from the NICE DCV external authenticator."
    request_token=$(echo "${user_token_request}" | jq -r .requestToken)
    access_file=$(echo "${user_token_request}" | jq -r .accessFile)
    _log "Request Token and Access File name obtained successfully."

    # Create the access file in the AUTHORIZATION_FILE_DIR with 644 permissions
    # This is used by the external authenticator to verify the user declares himself as who he really is
    _log "Creating access file.."
    _atomic_touch "${AUTHORIZATION_FILE_DIR}/${access_file}"
    _log "Access file created successfully."

    # Retrieve Session Token
    _log "Retrieving Session Token.."
    session_token_request=$(curl --retry 3 --max-time 5 -s -k -X GET -G "https://localhost:${ext_auth_port}" -d action=sessionToken -d requestToken="${request_token}")
    _validate_json "${session_token_request}" "Unable to obtain the Session Token from the NICE DCV external authenticator."
    session_token=$(echo "${session_token_request}" | jq -r .sessionToken)
    _log "Session Token obtained successfully."

    if [[ -z "${dcv_server_port}" ]]; then
      dcv_server_port=8443
    fi

    _log "--- NICE DCV authentication performed successfully. ---"
    echo "PclusterDcvServerPort=${dcv_server_port} PclusterDcvSessionId=${sessionid} PclusterDcvSessionToken=${session_token}"
}

main "$@"


#!/bin/bash
# Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the
# License. A copy of the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
# OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
# limitations under the License.

# GPU Health Check
# Check GPU healthiness by executing NVIDIA DCGM diagnostic tool
# If GPU_DEVICE_ORDINAL is set, the diagnostic check targets the GPU listed in the variable
# Prerequisite for the diagnostic check are:
#   * node has NVIDIA GPU
#   * DCGM service is running
#   * fabric manager service is running (if node is NVSwitch enabled)
#   * persistent mode is enabled for the target GPU

## DCGMI run level
# 1 - Quick (System Validation)
# 2 - Medium (Extended System Validation)
# 3 - Long (System HW Diagnostics)
# 4 - Extended (Longer-running System HW Diagnostics)
DCGMI_LEVEL=2

function main() {

  ## Identify full path of lspci command
  lspci_fullpath=$(PATH=$PATH:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:/bin command -v lspci)

  ## Check if the instance has an NVIDIA acceleration
  is_nvidia
  fast_success_exit "$?" "The GPU Health Check is running in an instance without a supported GPU: skipping its execution."

  ## Check if the nvidia-smi command is available
  command -v nvidia-smi &>/dev/null
  fast_success_exit "$?" "The GPU Health Check has been executed but the nvidia-smi tool is not available in this system or is located in a different file path. Please consider using an official ParallelCluster AMI."

  ## Check if the DCGM Diagnostic tool is available
  command -v dcgmi  &>/dev/null
  fast_success_exit "$?" "The GPU Health Check has been executed but the NVIDIA DCGM Diagnostic tool is not available in this system or is located in a different file path. Please consider using an official ParallelCluster AMI."

  ## Check if the NVIDIA GPU is supported by DCGM tool
  nvidia_smi_output=$(nvidia-smi -L)
  fast_success_exit "$?" "The nvidia-smi tool failed its execution."
  log_info "GPU list is '${nvidia_smi_output//$'\n'/, }'"
  is_dcgm_supported
  fast_success_exit "$?" "The GPU Health Check is running in a NVIDIA GPU not supported by DCGM Diagnostic tool: skipping its execution."

  ## Check if there are GPU associated to the job
  if [ -z "$GPU_DEVICE_ORDINAL" ]; then
    # The "nvidia-smi -L" return the list of GPUs available on the node:
    # $nvidia-smi -L
    # GPU 0: NVIDIA A100-SXM4-80GB (UUID: GPU-e57350fc-1789-b69a-0637-18a3025b1da2)
    # GPU 1: NVIDIA A100-SXM4-80GB (UUID: GPU-c4798aff-efab-68a7-1a52-107a0d6775fd)
    # ...
    # for each line in the output the ID is removing all characters after the colon symbol and the "GPU " string
    # $nvidia-smi -L | while IFS= read -r gpu ; do echo $gpu | cut -d ':' -f1 | sed s/"GPU "//; done
    # 0
    # 1
    # Then the newline is replaced by a comma
    # $nvidia-smi -L | while IFS= read -r gpu ; do echo $gpu | cut -d ':' -f1 | sed s/"GPU "//; done | tr '\n' ','
    # 0,1,
    # and then we remove the last char
    # $nvidia-smi -L | while IFS= read -r gpu ; do echo $gpu | cut -d ':' -f1 | sed s/"GPU "//; done | tr '\n' ',' | sed 's/.$//'
    # 0,1
    GPU_DEVICE_ORDINAL=$(echo "$nvidia_smi_output" | while IFS= read -r gpu ; do echo $gpu | cut -d ':' -f1 | sed s/"GPU "//; done | tr '\n' ',' | sed 's/.$//')
    log_info "The variable GPU_DEVICE_ORDINAL has been initialized using information provided by nvidia-smi"
  fi
  log_info "The value of GPU_DEVICE_ORDINAL is '${GPU_DEVICE_ORDINAL}'"

  ## Services Initialization
  local dcgm_service_started
  initialize_dcgm
  fast_success_exit "$?" "Skipping execution of the GPU Health Check"

  local nvidia_fabricmanager_service_started
  initialize_nvidia_fabricmanager
  fast_success_exit "$?" "Skipping execution of the GPU Health Check"

  ## Retrieve gpus information and enable them to run the health check
  local gpus_list
  gpus_list=${GPU_DEVICE_ORDINAL//,/$'\n'}
  for gpu_id in $gpus_list
  do
    # Retrieve gpu information. This check will exit in following cases:
    # - the nvidia-smi command is not available
    # - the nvidia-smi command is available but it fails its execution
    nvidia_smi_out=$(nvidia-smi -q -i $gpu_id)
    fast_success_exit "$?" "The nvidia-smi tool failed its execution."

    ## Enable persistence mode in target GPU, required by the DCGM Diagnostic tool
    local persistence_mode_enabled_$gpu_id
    persistence_mode=$(echo "$nvidia_smi_out" | grep "Persistence Mode")
    if [[ ! ${persistence_mode} == *"Enabled"* ]]; then
      nvidia-smi -i $gpu_id -pm 1 >/dev/null
      log_info "Persistence mode has been enabled by the GPU Health Check in target GPU '${gpu_id}'"
      declare persistence_mode_enabled_$gpu_id=true
    else
      log_info "Persistence mode already enabled for target GPU '${gpu_id}'"
    fi

    ## Log useful GPU information
    gpu_identifier=$(echo "$nvidia_smi_out" | grep "^GPU " | xargs)
    gpu_uuid=$(echo "$nvidia_smi_out" | grep "GPU UUID" | xargs)
    gpu_serial_number=$(echo "$nvidia_smi_out" | grep "Serial Number" | xargs)
    # This command logs the GPU information in the format
    # Details for GPU '0'. 'GPU 00000000:00:1E.0', 'GPU UUID : GPU-931c0765-01f6-f7af-00df-fb4202cec8b9', 'Serial Number : 0322817127524'
    log_info "Details for GPU '${gpu_id}': '${gpu_identifier}', '${gpu_uuid}', '${gpu_serial_number}'"
  done

  ## Run GPUs Health Check test
  log_info "Running GPU Health Check with DCGMI level $DCGMI_LEVEL"
  dcgmi diag -i $GPU_DEVICE_ORDINAL -r $DCGMI_LEVEL
  dcgmi_exit_code=$?

  if [ $dcgmi_exit_code -ne 0 ]; then
    log_error "The GPU Health Check failed"
  else
    log_info "The GPU Health Check succeeded"
  fi
  tear_down
  return $dcgmi_exit_code
}

function _log() {
  echo "$(date +"%Y-%m-%d %H:%M:%S,%3N") - [${0##*/}] - $1 - JobID $SLURM_JOB_ID - ${*:2}"
}

function log_info() {
  _log "INFO" "$@"
}

function log_error() {
  _log "ERROR" "$@"
}

function fast_success_exit() {
  if [ $1 -ne 0 ]; then
    log_info "$2"
    tear_down
    exit 0
  fi
}

function is_nvidia() {
  $lspci_fullpath | grep -i -o 'NVIDIA' &>/dev/null
  return $?
}

function is_dcgm_supported() {
  echo $nvidia_smi_output | grep -v -i -o ' K520 ' &>/dev/null
  return $?
}

function start_service() {
  systemctl start $1
  if [ $? -ne 0 ]; then
    log_info "Unable to start service $1"
    return 1
  fi
}

function initialize_dcgm() {
  systemctl show nvidia-dcgm.service -p ActiveState | grep "=active"
  if [ $? -ne 0 ]; then
    log_info "Starting nvidia-dcgm service since it is not running"
    start_service nvidia-dcgm
    if [ $? -ne 0 ]; then
      return 1
    fi
    dcgm_service_started=true
  else
    log_info "nvidia-dcgm service if already running"
  fi
}

function initialize_nvidia_fabricmanager() {
  log_info "Verifying if the check is running on a NVSwitch enabled system"
  nvswitch_check=$($lspci_fullpath -d 10de:1af1 | wc -l)
  if [ "${nvswitch_check}" -gt 1 ]; then
    systemctl show nvidia-fabricmanager.service -p ActiveState | grep "=active"
    if [ $? -ne 0 ]; then
      log_info "Starting nvidia-fabricmanager since it is not running"
      start_service nvidia-fabricmanager
      if [ $? -ne 0 ]; then
        return 1
      fi
      nvidia_fabricmanager_service_started=true
    else
      log_info "nvidia-fabricmanager is already running"
    fi
  fi
}

function tear_down() {
  log_info "Restoring previous configurations"

  ## Restore previous persistence mode
  for gpu_id in $gpus_list
  do
      persistence_mode_variable=persistence_mode_enabled_$gpu_id
      if [ "${!persistence_mode_variable}" = true ] ; then
        log_info "Disabling persistence mode on GPU '$gpu_id'"
        nvidia-smi -i $gpu_id -pm 0 >/dev/null
      fi
  done

  if [ "$nvidia_fabricmanager_service_started" = true ] ; then
    log_info "Stopping nvidia-fabricmanager service"
    systemctl stop nvidia-fabricmanager
  fi

  if [ "$dcgm_service_started" = true ] ; then
    log_info "Stopping nvidia-dcgm service"
    systemctl stop nvidia-dcgm
  fi
}

main "$@"


# Prefer a user pyenv over a system-wide install
if [ -s "${HOME}/.pyenv/bin" ]; then
    pyenv_root="${HOME}/.pyenv"
    pyenv_init="pyenv init -"
elif [ -s "<%= @global_prefix %>" ]; then
    pyenv_root="<%= @global_prefix %>"
    export PYENV_ROOT=${pyenv_root}
    # Rehashing will fail in a system install
    pyenv_init="pyenv init - --no-rehash"
fi

if [ -n "$pyenv_root" ]; then
    export PATH="${pyenv_root}/bin:$PATH"
    eval "$($pyenv_init)"
fi


#!/bin/bash
echo "*** Fix relative paths in dependent cookbooks"
rm -rf /tmp/cookbooks
mkdir -p /tmp/cookbooks/aws-parallelcluster-awsbatch
mkdir -p /tmp/cookbooks/aws-parallelcluster-platform
mkdir -p /tmp/cookbooks/aws-parallelcluster-environment
mkdir -p /tmp/cookbooks/aws-parallelcluster-computefleet
mkdir -p /tmp/cookbooks/aws-parallelcluster-shared/test
mkdir -p /tmp/cookbooks/aws-parallelcluster-slurm/test
cp -r cookbooks/aws-parallelcluster-awsbatch/test /tmp/cookbooks/aws-parallelcluster-awsbatch
cp -r cookbooks/aws-parallelcluster-platform/test /tmp/cookbooks/aws-parallelcluster-platform
cp -r cookbooks/aws-parallelcluster-environment/test /tmp/cookbooks/aws-parallelcluster-environment
cp -r cookbooks/aws-parallelcluster-computefleet/test /tmp/cookbooks/aws-parallelcluster-computefleet
cp -r cookbooks/aws-parallelcluster-shared/test /tmp/cookbooks/aws-parallelcluster-shared
cp -r cookbooks/aws-parallelcluster-slurm/test /tmp/cookbooks/aws-parallelcluster-slurm
sed -i.bak "s#path: ../aws-parallelcluster#path: /tmp/cookbooks/aws-parallelcluster#g" /tmp/cookbooks/aws-parallelcluster-*/test/inspec.yml


#!/bin/bash

KITCHEN_SCOPE=$1; shift;
KITCHEN_SUBJECT=$(echo $KITCHEN_SCOPE | awk -F- '{print $1}')
KITCHEN_PHASE=$(echo $KITCHEN_SCOPE | awk -F- '{print $2}')

# ./kitchen.$driver.sh platform-install ... will run kitchen in aws-parallelcluster-cookbook/cookbooks/aws-parallelcluster-platform dir
export KITCHEN_COOKBOOK_PATH="cookbooks/aws-parallelcluster-${KITCHEN_SUBJECT}"

export KITCHEN_LOCAL_YAML="${KITCHEN_COOKBOOK_PATH}/kitchen.${KITCHEN_SCOPE}.yml"
export KITCHEN_PHASE


#!/bin/bash

export KITCHEN_HOOK=$1
KITCHEN_ROOT_DIR=$(readlink -f $2)
export KITCHEN_ROOT_DIR
THIS_DIR=$(dirname "$0")

# Run OS specific hooks from kitchen/hooks folder
SCRIPT="${THIS_DIR}/hooks/${KITCHEN_PLATFORM_NAME}/${KITCHEN_HOOK}.sh"
if [ -e "${SCRIPT}" ]; then

  echo "*** Run ${KITCHEN_HOOK} on host ${KITCHEN_INSTANCE_HOSTNAME}"
  echo "**** Script: ${SCRIPT}"
  # shellcheck disable=SC1090
  source "${SCRIPT}"
fi

# Run test specific hooks from cookbooks/aws-parallelcluster-*/test/hooks folder
SCRIPT="${THIS_DIR}/../${KITCHEN_COOKBOOK_PATH}/test/hooks/${KITCHEN_PHASE}/${KITCHEN_SUITE_NAME}/${KITCHEN_HOOK}.sh"
if [ -e "${SCRIPT}" ]; then
  echo "*** Run ${KITCHEN_HOOK} for cookbook ${KITCHEN_COOKBOOK_PATH} suite ${KITCHEN_SUITE_NAME} on host ${KITCHEN_INSTANCE_HOSTNAME}"
  echo "**** Script: ${SCRIPT}"

  if [ "${KITCHEN_DRIVER}" = "ec2" ]; then

    if [ -n "${KITCHEN_INSTANCE_HOSTNAME}" ]; then
      case ${KITCHEN_PLATFORM_NAME} in
        alinux*|redhat* ) export KITCHEN_EC2_USER='ec2-user';;
        centos*         ) export KITCHEN_EC2_USER='centos';;
        ubuntu*         ) export KITCHEN_EC2_USER='ubuntu';;
      esac
      echo "Retrieve EC2 instance id using key ${KITCHEN_SSH_KEY_PATH}"

      KITCHEN_EC2_INSTANCE_ID=$(ssh -o StrictHostKeyChecking=no -i "${KITCHEN_SSH_KEY_PATH}" \
        "${KITCHEN_EC2_USER}@${KITCHEN_INSTANCE_HOSTNAME}" '
        TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600") \
        && curl -H "X-aws-ec2-metadata-token: $TOKEN" -v http://169.254.169.254/latest/meta-data/instance-id
      ')

      [ -n "${KITCHEN_EC2_INSTANCE_ID}" ] && echo "EC2 instance id: ${KITCHEN_EC2_INSTANCE_ID}" || echo "Unable to retrieve instance id."
      export KITCHEN_EC2_INSTANCE_ID
    fi
  fi

  # shellcheck disable=SC1090
  source "${SCRIPT}"

else
  echo "*** Hook ${KITCHEN_HOOK} undefined for ${KITCHEN_SUITE_NAME}"
fi


#!/bin/bash
# do some check
>&2 echo "err to stderr"
echo "output to stdout"
exit 125


#!/bin/bash
# do some check
>&2 echo "err to stderr"
echo "output to stdout"
exit 0


#!/bin/bash

set -ex

# On Mac OS, the default implementation of sed is BSD sed, but this script requires GNU sed.
if [ "$(uname)" == "Darwin" ]; then
  command -v gsed >/dev/null 2>&1 || { echo >&2 "[ERROR] Mac OS detected: please install GNU sed with 'brew install gnu-sed'"; exit 1; }
  PATH="/usr/local/opt/gnu-sed/libexec/gnubin:$PATH"
fi

if [ -z "$1" -o -z "$2" ]; then
    echo "New version not specified. Usage: bump-version.sh NEW_PCLUSTER_VERSION NEW_AWSBATCH_CLI_VERSION"
    exit 1
fi

NEW_PCLUSTER_VERSION=$1
NEW_AWSBATCH_CLI_VERSION=$2
VERSIONS_FILE="cookbooks/aws-parallelcluster-shared/attributes/versions.rb"

CURRENT_PCLUSTER_VERSION=$(sed -ne "s/default\['cluster'\]\['parallelcluster-version'\] = '\(.*\)'/\1/p" ${VERSIONS_FILE})

NEW_PCLUSTER_VERSION_SHORT=$(echo ${NEW_PCLUSTER_VERSION} | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+")
CURRENT_PCLUSTER_VERSION_SHORT=$(echo ${CURRENT_PCLUSTER_VERSION} | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+")

sed -i "s/default\['cluster'\]\['parallelcluster-version'\] = '${CURRENT_PCLUSTER_VERSION}'/default['cluster']['parallelcluster-version'] = '${NEW_PCLUSTER_VERSION}'/g" ${VERSIONS_FILE}
sed -i "s/default\['cluster'\]\['parallelcluster-cookbook-version'\] = '$CURRENT_PCLUSTER_VERSION'/default['cluster']['parallelcluster-cookbook-version'] = '${NEW_PCLUSTER_VERSION}'/g" ${VERSIONS_FILE}
sed -i "s/default\['cluster'\]\['parallelcluster-node-version'\] = '${CURRENT_PCLUSTER_VERSION}'/default['cluster']['parallelcluster-node-version'] = '${NEW_PCLUSTER_VERSION}'/g" ${VERSIONS_FILE}
sed -i "s/version '${CURRENT_PCLUSTER_VERSION_SHORT}'/version '${NEW_PCLUSTER_VERSION_SHORT}'/g" metadata.rb

sed -i "s/ENV\['KITCHEN_PCLUSTER_VERSION'\] || '${CURRENT_PCLUSTER_VERSION}'/ENV\['KITCHEN_PCLUSTER_VERSION'\] || '${NEW_PCLUSTER_VERSION}'/g" kitchen.ec2.yml

COOKBOOKS=("aws-parallelcluster-awsbatch" "aws-parallelcluster-entrypoints" "aws-parallelcluster-slurm" "aws-parallelcluster-platform" "aws-parallelcluster-environment" "aws-parallelcluster-computefleet" "aws-parallelcluster-shared" "aws-parallelcluster-slurm" "aws-parallelcluster-tests")
for cookbook in "${COOKBOOKS[@]}"; do
  METADATA_FILES+=("cookbooks/${cookbook}/metadata.rb")
done

# Update version in specific cookbook metadata
sed -i "s/version '${CURRENT_PCLUSTER_VERSION_SHORT}'/version '${NEW_PCLUSTER_VERSION_SHORT}'/g" ${METADATA_FILES[*]}

for COOKBOOK in ${COOKBOOKS[*]}; do
  # Update dependencies version in main cookbook metadata
  sed -i "s/depends '${COOKBOOK}', '~> ${CURRENT_PCLUSTER_VERSION_SHORT}'/depends '${COOKBOOK}', '~> ${NEW_PCLUSTER_VERSION_SHORT}'/g" metadata.rb
  # Update dependencies version in specific cookbook metadata
  sed -i "s/depends '${COOKBOOK}', '~> ${CURRENT_PCLUSTER_VERSION_SHORT}'/depends '${COOKBOOK}', '~> ${NEW_PCLUSTER_VERSION_SHORT}'/g" ${METADATA_FILES[*]}
done

# Update AWS Batch CLI version
CURRENT_AWSBATCH_CLI_VERSION=$(sed -ne "s/^default\['cluster'\]\['parallelcluster-awsbatch-cli-version'\] = '\(.*\)'/\1/p" ${VERSIONS_FILE})
sed -i "s/default\['cluster'\]\['parallelcluster-awsbatch-cli-version'\] = '${CURRENT_AWSBATCH_CLI_VERSION}'/default['cluster']['parallelcluster-awsbatch-cli-version'] = '${NEW_AWSBATCH_CLI_VERSION}'/g" ${VERSIONS_FILE}