diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..2c97574 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,38 @@ +name: test aws action for put parameter store +permissions: + contents: write + actions: write +on: + push: + branches: + - dev + - main +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: test parameter store - input env + uses: ./ + with: + parameters: test=test + + - name: test parameter store - input multiline env + uses: ./ + with: + parameters: | + test=test + test1="test1" + + - name: test parameter store - input variables + uses: ./ + env: + ENV_VAL: ${{ github.workspace }} + with: + parameters: | + test=test + test1="test1" + env_val=${{ env.ENV_VAL }} + diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..1d7a709 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +python 3.12.3 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d98b29e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.12.3-slim + +RUN apt-get update +RUN pip install --upgrade pip + +COPY requirements.txt . +RUN pip install -r requirements.txt + +WORKDIR /usr/src + +COPY ./src/ . + +ENTRYPOINT ["python", "/usr/src/aws_ssm.py"] \ No newline at end of file diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..c726c4f --- /dev/null +++ b/action.yml @@ -0,0 +1,15 @@ +name: 'AWS SSM' +description: 'A Github action for parameter store update' +inputs: + parameters: + description: 'Provide parameters as json or .env file format with each pair for one line.' + required: false + params-file: + description: 'Write params to a json file and provide the file path from github workspace root.' + required: false +runs: + using: docker + image: Dockerfile + env: + INPUT_PARAMS: ${{ inputs.parameters }} + INPUT_PARAMS_FILE_PATH: ${{ inputs.params-file }} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1db657b --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +boto3 \ No newline at end of file diff --git a/src/aws_ssm.py b/src/aws_ssm.py new file mode 100644 index 0000000..68fe02e --- /dev/null +++ b/src/aws_ssm.py @@ -0,0 +1,28 @@ +import os +import pathlib + +from inputs import AwsParameter, parse_input_params +from common.aws_clients import get_client + + +def run(aws_parameters: dict[str, AwsParameter]): + ssm_client = get_client(service_name='ssm', + region_name=os.getenv('AWS_REGION'), + aws_access_key=os.getenv('AWS_ACCESS_KEY'), + aws_secret_key=os.getenv('AWS_SECRET_KEY'), + ) + for name, param in aws_parameters.items(): + ssm_client.put_parameter(Name=param.name, Value=param.value, Type=param.param_type, Overwrite=param.overwrite) + + +if __name__ == '__main__': + input_params = os.getenv('INPUT_PARAMS') + params_inline = parse_input_params(input_params) if input_params is not None else {} + params_from_file = {} + params_file_path = os.getenv('INPUT_PARAMS_FILE_PATH') + if params_file_path is not None and params_file_path != "": + path = pathlib.PurePath(os.getenv('GITHUB_WORKSPACE'), params_file_path) + with open(path, 'r') as f: + params_from_file = parse_input_params(f.read()) + params = {**params_from_file, **params_inline} + run(params) diff --git a/src/common/__init__.py b/src/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/common/aws_clients.py b/src/common/aws_clients.py new file mode 100644 index 0000000..e89e36a --- /dev/null +++ b/src/common/aws_clients.py @@ -0,0 +1,9 @@ +import os + +import boto3 + + +def get_client(service_name: str, region_name: str, aws_access_key: str, aws_secret_key: str): + return boto3.client(service_name=service_name, region_name=region_name, + aws_access_key_id=aws_access_key, + aws_secret_access_key=aws_secret_key) diff --git a/src/inputs.py b/src/inputs.py new file mode 100644 index 0000000..83ca5ff --- /dev/null +++ b/src/inputs.py @@ -0,0 +1,48 @@ +import json +from dataclasses import dataclass + + +@dataclass(init=True) +class AwsParameter: + name: str + value: str + param_type: str = 'String' + overwrite: bool = True + + +def ensure_json_input(input_params: str): + try: + return json.loads(input_params) + except ValueError as e: + print('Not valid JSON') + return None + + +def env_to_param(env_line: str) -> AwsParameter: + env_line = env_line.strip() + env_line = env_line.split(sep="=", maxsplit=1) + return AwsParameter(name=env_line[0], value=env_line[1]) + + +def ensure_env_input(input_params: str): + param_lines = input_params.strip().split(sep="\n") + return [param_line.strip() for param_line in param_lines] + + +def value_to_aws_parameter(param_name, param_value): + parsed_value_json = ensure_json_input(param_value) + if parsed_value_json is None: + return AwsParameter(name=param_name, value=param_value) + value = parsed_value_json.get('value') if parsed_value_json.get('value') is not None else None + param_type = parsed_value_json.get('type') if parsed_value_json.get('type') is not None else 'String' + overwrite = parsed_value_json.get('overwrite') if parsed_value_json.get('overwrite') is not None else True + return AwsParameter(name=param_name, value=value, param_type=param_type, overwrite=overwrite) + + +def parse_input_params(input_params: str): + parsed_params_json = ensure_json_input(input_params) + if parsed_params_json is not None: + return {key: value_to_aws_parameter(key, value) for key, value in parsed_params_json.items()} + parsed_params_env = ensure_env_input(input_params) + parsed_aws_params_env = [env_to_param(env_param) for env_param in parsed_params_env] + return {p.name: p for p in parsed_aws_params_env}