Skip to content

Commit

Permalink
Merge pull request #14 from im-open/transfer
Browse files Browse the repository at this point in the history
ARCH-1919 - Transfer to Infra-Purple
  • Loading branch information
danielle-casella-adams authored Oct 17, 2023
2 parents 8b49cb7 + fe34501 commit e2694d0
Show file tree
Hide file tree
Showing 13 changed files with 698 additions and 146 deletions.
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1 +1 @@
* @im-open/swat
* @im-open/infra-purple
288 changes: 288 additions & 0 deletions .github/workflows/build-and-review-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,291 @@ jobs:
# The npm script to run to build the action. This is typically 'npm run build' if the
# action needs to be compiled. For composite-run-steps actions this is typically empty.
build-command: 'npm run build'

test:
runs-on: ubuntu-latest

env:
ORG: 'im-open'
THIS_REPO: 'setup-deploy-keys'
INTERNAL_REPO_TO_CLONE: 'im-open/internal-repo-for-testing'
INTERNAL_REPO_TO_CLONE_DIR: 'internal-repo-for-testing'

# The private key which will be used to clone the internal & empty deploy-keys-testing repo.
# The repo does not contain anything sensitive, it is just marked as internal so the key can be tested here.
# This SSH key was generated as a readonly key (so no push abilities).
INTERNAL_REPO_TO_CLONE_KEY: ${{ secrets.SSH_KEY_TESTING_REPO }}
SSH_DEPLOY_KEY_INFO: |
[
{ "orgAndRepo": "im-open/internal-repo-for-testing", "envName" : "INTERNAL_REPO_TO_CLONE_KEY" }
]
steps:
#--------------------------------------
# SETUP
#--------------------------------------
- name: Fail test job if fork
run: |
if [ "${{ github.event.pull_request.head.repo.fork }}" == "true" ]; then
echo "This test job requires secrets that PRs from forks will not have access to. Before this PR can be merged, the tests should be run on an intermediate branch created by repository owners."
exit 1
fi
- name: '-------------------------------------------------------------------------------------------------------'
run: echo ""

- name: Setup - Checkout the action
if: always()
uses: actions/checkout@v3

- name: Setup - Verify the internal repo to clone dir does not exist
if: always()
run: ./test/assert-dir-does-not-exist.sh --path "./${{ env.INTERNAL_REPO_TO_CLONE_DIR }}"

- name: Setup - Verify the ssh-agent is not running
if: always()
run: ./test/assert-ssh-agent-is-not-running.sh

- name: Setup - Verify .gitconfig does not contain any insteadOf urls (because it does not exist)
if: always()
run: |
if [ -f "~/.gitconfig" ]
then
echo "The .gitconfig file exists which is unexpected"
exit 1
else
echo "The .gitconfig file does not exist which is expected. No insteadOf rules exist."
exit 0
fi
- name: Setup - Verify the ssh config has no configuration (because it does not exist)
working-directory: /home/runner
if: always()
run: |
TARGET_FILE=".ssh/config"
if [ -f "$TARGET_FILE" ]
then
echo "$TARGET_FILE exists which is NOT expected. Contents:"
cat $TARGET_FILE
exit 1
else
echo "$TARGET_FILE does not exist which is expected."
fi
#--------------------------------------------
# VALIDATE STARTING CONDITIONS/NO PERMISSIONS
#--------------------------------------------
- name: '-------------------------------------------------------------------------------------------------------'
run: echo ""

- name: When an internal repo is cloned with the default PAT
if: always()
id: internal-failure
continue-on-error: true # This is needed because we expect the step to fail. We need it to "pass" in order for the test job to succeed.
run: git clone [email protected]:${{ env.INTERNAL_REPO_TO_CLONE }}.git

- name: Then the outcome should be failure because of lack of permissions to clone an internal repo
if: always()
run: ./test/assert-values-match.sh --name "step outcome" --expected "failure" --actual "${{ steps.internal-failure.outcome }}"

- name: And the directory for the internal repo to clone should not exist
if: always()
run: ./test/assert-dir-does-not-exist.sh --path "./${{ env.INTERNAL_REPO_TO_CLONE_DIR }}"

- name: And the ssh-agent should be running
if: always()
run: ./test/assert-ssh-agent-is-running.sh

#--------------------------------------------
# SETUP DEPLOY KEYS AND CLONE INTERNAL REPO
#--------------------------------------------
- name: '-------------------------------------------------------------------------------------------------------'
run: echo ""

- name: When setup-deploy-keys is run with a key for the internal repo
if: always()
id: setup-deploy-keys
uses: ./
with:
deploy-key-info: ${{ env.SSH_DEPLOY_KEY_INFO }}

- name: And the internal repo is cloned
if: always()
id: internal-success
run: git clone [email protected]:${{ env.INTERNAL_REPO_TO_CLONE }}.git

- name: Then the outcome should be success
if: always()
run: ./test/assert-values-match.sh --name "step outcome" --expected "success" --actual "${{ steps.internal-success.outcome }}"

- name: And the ssh-agent should be running
if: always()
run: ./test/assert-ssh-agent-is-running.sh

- name: And the directory for the internal repo to clone dir should exist
if: always()
run: ./test/assert-dir-exists.sh --path "./${{ env.INTERNAL_REPO_TO_CLONE_DIR }}"

- name: And the .gitconfig should exist with 3 entries for ${{ env.INTERNAL_REPO_TO_CLONE }} insteadOf urls
if: always()
run: |
configEntries=$(git config --get-regexp "${{ steps.setup-deploy-keys.outputs.key-filename }}")
count=0
while IFS=$'\n' read -ra ENTRIES; do
for i in "${ENTRIES[@]}"; do
count=$((count+1))
echo -e "\nEntry $count: '$i'"
done
done <<< "$configEntries"
if [ $count -ne 3 ]
then
echo -e "\nExpected 3 insteadOf entries in .gitconfig but found $count"
exit 1
else
echo -e "\nThere were 3 insteadOf entries in the .gitconfig as expected."
fi
- name: And the ssh config should exist with an entry for ${{ env.INTERNAL_REPO_TO_CLONE }}
if: always()
run: |
TARGET_FILE="/home/runner/.ssh/config"
if [ -f "$TARGET_FILE" ]
then
echo "$TARGET_FILE exists which is expected."
actualContent=$(cat $TARGET_FILE)
else
echo "$TARGET_FILE does not exist which is not expected"
exit 1
fi
rawExpectedContent=$(cat ./test/files/expected-ssh-host.txt)
hostKey="${{ steps.setup-deploy-keys.outputs.key-filename }}"
expectedContent=$(echo "${rawExpectedContent//REPLACEME/"$hostKey"}" )
./test/assert-values-match.sh --name "ssh config" --expected "$expectedContent" --actual "$actualContent"
#--------------------------------------------
# UN-PARSEABLE INPUT
#--------------------------------------------
- name: '-------------------------------------------------------------------------------------------------------'
run: echo ""

- name: When setup-deploy-keys is run with an un-parseable input
uses: ./
if: always()
id: unparseable
continue-on-error: true # This is needed because we expect the step to fail. We need it to "pass" in order for the test job to succeed.
with:
deploy-key-info: '[ orgAndRepo=im-open/internal-repo-for-testing, envName=INTERNAL_REPO_TO_CLONE_KEY ]'

- name: Then the outcome should be failure
if: always()
run: ./test/assert-values-match.sh --name "step outcome" --expected "failure" --actual "${{ steps.unparseable.outcome }}"

- name: And the validation-error output should be 'argument-parsing'
if: always()
run: ./test/assert-values-match.sh --name "validation-error" --expected "argument-parsing" --actual "${{ steps.unparseable.outputs.validation-error }}"

#--------------------------------------------
# EMPTY ARRAY
#--------------------------------------------
- name: '-------------------------------------------------------------------------------------------------------'
run: echo ""

- name: When setup-deploy-keys is run with an empty array
uses: ./
if: always()
id: empty-array
continue-on-error: true # This is needed because we expect the step to fail. We need it to "pass" in order for the test job to succeed.
with:
deploy-key-info: '[]'

- name: Then the outcome should be failure
if: always()
run: ./test/assert-values-match.sh --name "step outcome" --expected "failure" --actual "${{ steps.empty-array.outcome }}"

- name: And the validation-error output should be 'empty-keys'
if: always()
run: ./test/assert-values-match.sh --name "validation-error" --expected "empty-keys" --actual "${{ steps.empty-array.outputs.validation-error }}"

#--------------------------------------------
# EMPTY orgAndRepo ARG
#--------------------------------------------
- name: '-------------------------------------------------------------------------------------------------------'
run: echo ""

- name: When setup-deploy-keys is run with a missing orgAndRepo
uses: ./
if: always()
id: missing-orgAndRepo
continue-on-error: true # This is needed because we expect the step to fail. We need it to "pass" in order for the test job to succeed.
with:
deploy-key-info: |
[
{ "orgAndRepo": "", "envName" : "INTERNAL_REPO_TO_CLONE_KEY" }
]
- name: Then the outcome should be failure
if: always()
run: ./test/assert-values-match.sh --name "step outcome" --expected "failure" --actual "${{ steps.missing-orgAndRepo.outcome }}"

- name: And the validation-error output should be 'missing-orgAndRepo'
if: always()
run: ./test/assert-values-match.sh --name "validation-error" --expected "missing-orgAndRepo" --actual "${{ steps.missing-orgAndRepo.outputs.validation-error }}"

#--------------------------------------------
# EMPTY envName ARG
#--------------------------------------------
- name: '-------------------------------------------------------------------------------------------------------'
run: echo ""

- name: When setup-deploy-keys is run with a missing envName
uses: ./
if: always()
id: missing-envName
continue-on-error: true # This is needed because we expect the step to fail. We need it to "pass" in order for the test job to succeed.
with:
deploy-key-info: |
[
{ "orgAndRepo": "im-open/internal-repo-for-testing", "envName" : "" }
]
- name: Then the outcome should be failure
if: always()
run: ./test/assert-values-match.sh --name "step outcome" --expected "failure" --actual "${{ steps.missing-envName.outcome }}"

- name: And the validation-error output should be 'missing-envName'
if: always()
run: ./test/assert-values-match.sh --name "validation-error" --expected "missing-envName" --actual "${{ steps.missing-envName.outputs.validation-error }}"

#--------------------------------------------
# UNPOPULATED ENV VAR FOR SECRET
#--------------------------------------------
- name: '-------------------------------------------------------------------------------------------------------'
run: echo ""

- name: When setup-deploy-keys is run with an unpopulated env var
uses: ./
if: always()
id: unpopulated-env
continue-on-error: true # This is needed because we expect the step to fail. We need it to "pass" in order for the test job to succeed.
with:
deploy-key-info: |
[
{ "orgAndRepo": "im-open/internal-repo-for-testing", "envName" : "UNPOPULATED_ENV_VARIABLE" }
]
- name: Then the outcome should be failure
if: always()
run: ./test/assert-values-match.sh --name "step outcome" --expected "failure" --actual "${{ steps.unpopulated-env.outcome }}"

- name: And the validation-error output should be 'unpopulated-env-var'
if: always()
run: ./test/assert-values-match.sh --name "validation-error" --expected "unpopulated-env-var" --actual "${{ steps.unpopulated-env.outputs.validation-error }}"

- name: '-------------------------------------------------------------------------------------------------------'
run: echo ""
43 changes: 41 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,23 @@ This modified action will take care of:
- Adding `insteadOf` rules in the git config. These rules will rewrite git urls it encounters for the provided org/repo and point to the applicable alias entry that was added to the ssh config. This will ensure it uses the right deploy key when interacting with different private repositories.

These entries and `insteadOf` rules are necessary when using multiple deploy keys because of the way GitHub handles requests. From [webfactory/ssh-agent]:
> When using Github deploy keys, GitHub servers will accept the first known key. But since deploy keys are scoped to a single repository, this might not be the key needed to access a particular repository. Thus, you will get the error message fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists. if the wrong key/repository combination is tried.
> When using Github deploy keys, GitHub servers will accept the first known key. But since deploy keys are scoped to a single repository, this might not be the key needed to access a particular repository. Thus, you will get the error message fatal: "*Could not read from remote repository. Please make sure you have the correct access rights and the repository exists*" if the wrong key/repository combination is tried.
More details on context, usage, troubleshooting or known issues and limitations can be found on the original repository [webfactory/ssh-agent].

## Index <!-- omit in toc -->

- [Setup Deploy Keys](#setup-deploy-keys)
- [Inputs](#inputs)
- [Outputs](#outputs)
- [validation-error Output](#validation-error-output)
- [Usage Examples](#usage-examples)
- [Contributing](#contributing)
- [Incrementing the Version](#incrementing-the-version)
- [Source Code Changes](#source-code-changes)
- [Recompiling Manually](#recompiling-manually)
- [Updating the README.md](#updating-the-readmemd)
- [Tests](#tests)
- [Code of Conduct](#code-of-conduct)
- [License](#license)

Expand All @@ -33,6 +36,35 @@ More details on context, usage, troubleshooting or known issues and limitations
|-------------------|-------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `deploy-key-info` | true | An array of deploy key info objects that contain the org/repo that the deploy key is intended for as well as the name of the environment variable that contains the private key's value. |

## Outputs

This action has two outputs which can be used by the workflows but are generally reserved for testing.
| Output | Description |
|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `validation-error` | A string with a short description of a validation error that occurred. Generally used for testing but can be used programmatically to detect which types of validation errors the action is encountering. |
| `key-filename` | The name of the file where the key was written to disk. Generally used for testing because the output only reflects the last key that was processed. |

### validation-error Output

The `validation-error` output will only contain one validation error at a time. If multiple keys or multiple error conditions exist, this output cannot reliably indicate those conditions. The output was designed to be used for testing specific error conditions and may not be suitable for regular production use.

- `argument-parsing` - This occurs when there is an error JSON parsing the `deploy-key-info` argument. It should be a string containing a parseable JSON array of objects that contain two arguments:

```yml
[
# orgAndRepo should contain the organization/repository that the deploy key is being setup for.
# envName is the name of an environment variable that contains the private key for accessing
# the orgAndRepo. It is not the value of the actual environment variable.
{ orgAndRepo: 'im-open/repo-1', envName: 'NAME_OF_ENV_WITH_PRIVATE_KEY_1'},
{ orgAndRepo: 'im-open/repo-2', envName: 'NAME_OF_ENV_WITH_PRIVATE_KEY_2'}
]
```

- `empty-keys` - The `deploy-key-info` argument was an empty array.
- `missing-orgAndRepo` - One of the provided deploy keys is missing the `orgAndRepo` argument.
- `missing-envName` - One of the provided deploy keys is missing the `envName` argument.
- `unpopulated-env-var` - One of the environment variables provided as an `envName` has not been populated.

## Usage Examples

```yml
Expand All @@ -54,7 +86,7 @@ jobs:

- name: Setup deploy keys for use with Terraform
# You may also reference just the major or major.minor version
uses: im-open/[email protected].3
uses: im-open/[email protected].4
with:
deploy-key-info: |
[
Expand Down Expand Up @@ -82,6 +114,7 @@ When creating PRs, please review the following guidelines:
- [ ] At least one of the commit messages contains the appropriate `+semver:` keywords listed under [Incrementing the Version] for major and minor increments.
- [ ] The action has been recompiled. See [Recompiling Manually] for details.
- [ ] The README.md has been updated with the latest version of the action. See [Updating the README.md] for details.
- [ ] Any tests in the [build-and-review-pr] workflow are passing

### Incrementing the Version

Expand Down Expand Up @@ -116,6 +149,12 @@ npm run build

If changes are made to the action's [source code], the [usage examples] section of this file should be updated with the next version of the action. Each instance of this action should be updated. This helps users know what the latest tag is without having to navigate to the Tags page of the repository. See [Incrementing the Version] for details on how to determine what the next version will be or consult the first workflow run for the PR which will also calculate the next version.

### Tests

The [build-and-review-pr] workflow includes tests which are linked to a status check. That status check needs to succeed before a PR is merged to the default branch. When a PR comes from a branch, the workflow has access to secrets which are required to run the tests successfully.

When a PR comes from a fork, the workflow cannot access any secrets, so the tests won't have the necessary permissions to run. When a PR comes from a fork, the changes should be reviewed, then merged into an intermediate branch by repository owners so tests can be run against the PR changes. Once the tests have passed, changes can be merged into the default branch.

## Code of Conduct

This project has adopted the [im-open's Code of Conduct](https://github.com/im-open/.github/blob/main/CODE_OF_CONDUCT.md).
Expand Down
Loading

0 comments on commit e2694d0

Please sign in to comment.