# Technical documentation — CodeDeployScripts

Evidence date: 2026-10-18. `master` at `06626b32e2a09034b7df2b94b9abae5809bc12e6` (2019-06-06 14:34:43 +0200). `development` at `dc915f471e87e29b745568c2a5aba62fb0e4dfd7` (2019-07-26 14:46:41 +0200).

Remote: `git@bitbucket.org:overdrive/codedeployscripts.git`. `origin/HEAD` is `origin/master`.

The scan-host path from the onboarding issue was not on this machine. This note is from a clone of both tips. Credential placeholders and the private key body are omitted on purpose.

## Architecture

There is no application framework and no database. The moving parts are a Bitbucket Pipelines file, one Python script, an AWS CodeDeploy AppSpec, three shell hooks, and one HTML file.

```text
push to master
  Bitbucket image python:3.5.7
    zip -r /tmp/artifact.zip *
    python codedeploy_deploy.py
      boto3 S3 put_object
      boto3 CodeDeploy create_deployment
      poll get_deployment
  EC2 (selected by the deployment group, not by this script)
    ApplicationStop   scripts/stop_server      runas root
    BeforeInstall     scripts/install_dependencies
    (copy)            /  ->  /var/www/html/
    ApplicationStart  scripts/start_server
    httpd serves index.html

push to development
  same image
    git ftp init     (active on the master copy of the yml)
    git ftp push     (active on the development copy of the yml)
    destination ftp://$FTP_HOST
```

CodeDeploy's own lifecycle has more hook points than this AppSpec fills. This file only names the three hooks that are present.

## Repository layout

Tracked paths on `master` (`git ls-files`). Blob sizes are from `git ls-tree -l`. The `development` tip differs only in `README.md`, `bitbucket-pipelines.yml`, and `index.html`.

| Path | Mode | Bytes on `master` | Role |
|---|---|---|---|
| `README.md` | 100644 | 3317 | Setup checklist for AWS and FTP |
| `appspec.yml` | 100644 | 368 | CodeDeploy file mapping and hooks |
| `bitbucket-pipelines.yml` | 100644 | 853 | Branch pipelines |
| `codedeploy_deploy.py` | 100644 | 3804 | S3 upload and CodeDeploy API |
| `index.html` | 100644 | 578 | Smoke page |
| `scripts/install_dependencies` | 100644 | 38 | `yum install httpd` |
| `scripts/start_server` | 100644 | 64 | `chmod 755` and `service httpd start` |
| `scripts/stop_server` | 100644 | 128 | Stop attempt and `chmod 777` |
| `silcodedeploy_evolvav.pem` | 100644 | 1670 | RSA private key. Unreferenced |

There is no `.gitignore`, no test directory, no `requirements.txt`, and no lockfile. boto3 is installed only by the pipeline `pip` line.

Shell scripts are not marked executable in git (`100644`). Whether the CodeDeploy agent requires the executable bit is **Unknown** from this repo. Each hook file starts with `#!/bin/bash`.

## Languages and runtime

| Piece | Runtime |
|---|---|
| Pipeline image | `python:3.5.7` for every step, including the FTP step |
| Deploy script | Python 2/3 style: `from __future__ import print_function`. Written against the 3.5.7 image |
| AWS calls | `boto3==1.3.0`, imported as `boto3`. Exception type imported: `botocore.exceptions.ClientError` |
| Hooks | bash, `yum`, `service`, `pgrep`, `chmod` |
| FTP step | `git` and `git-ftp` from apt, on Debian packages inside the Python image |
| Page | Static HTML. No JavaScript |

The `master` step comments say `apt-get update` is required to install zip, and boto3 is required for the script. The FTP step comments say zip and git are required "for packaging". That step never runs `zip`. Provenance: **Implemented** commands versus the comment on the FTP step.

## Pipeline

File: `bitbucket-pipelines.yml`.

Top-level `image: python:3.5.7`. No `options`, no caches, no services, no artifacts declaration, no `max-time`.

`master` step, in order:

```text
apt-get update
apt-get install -y zip
pip install boto3==1.3.0
zip -r /tmp/artifact.zip *
python codedeploy_deploy.py
```

`development` step, in order:

```text
apt-get update
apt-get -qq install zip git git-ftp
git ftp init|push -u "$FTP_USERNAME" -p "$FTP_PASSWORD" ftp://$FTP_HOST
```

On `master` at `06626b3`, init is uncommented and push is commented. On `development` at `dc915f4`, push is uncommented and init is commented.

`apt-get` is not passed `-y` on `update`. `install` on the master step is `-y`. The FTP install is `-qq` without an explicit `-y` on that line. Provenance: **Implemented**.

## Deploy script

File: `codedeploy_deploy.py`. Header: Amazon Bitbucket Builds template, copyright 2016, Apache License 2.0, `narshiva@amazon.com`, v1.0.0. Local history edited it on 2019-05-29 (error messages, a retries dict that was removed the same day). Drift from the upstream sample was not re-checked against that Bitbucket project on 2026-10-18.

### Module-level key

```text
VERSION_LABEL = strftime("%Y%m%d%H%M%S")
BUCKET_KEY = os.getenv('APPLICATION_NAME') + '/' + VERSION_LABEL + '-bitbucket_builds.zip'
```

This runs at import. `main` is only the upload call and the deploy call. Both use the same `BUCKET_KEY`.

### `upload_to_s3(artifact)`

- Client: `boto3.client('s3')` with no region and no retries argument.
- `put_object(Body=open(artifact, 'rb'), Bucket=os.getenv('S3_BUCKET'), Key=BUCKET_KEY)`.
- The file handle is not closed in the function.
- Returns False on `ClientError` or `IOError`. Returns True otherwise.
- `main` always passes the path `/tmp/artifact.zip`.

### `deploy_new_revision()`

- Client: `boto3.client('codedeploy')`.
- `create_deployment` fields: `applicationName`, `deploymentGroupName`, `deploymentConfigName`, S3 revision (`revisionType` `S3`, `bundleType` `zip`), description `New deployment from BitBucket`, `ignoreApplicationStopFailures=True`.
- `str()` is applied to the application name, group name, and config name. It is not applied to the bucket.
- Poll: `while 1` with `get_deployment(deploymentId=response['deploymentId'])`.
- `sleep` is imported and never called.
- Success text is `Deployment Succeeded`. Failure text is `Deployment Failed` plus the whole `deploymentInfo` dict.
- `main` maps a False return to `sys.exit(1)`.

`ClientError` is the only caught failure around the AWS calls. A missing `APPLICATION_NAME` fails earlier, at import, outside those handlers.

## AppSpec

```yaml
version: 0.0
os: linux
files:
  - source: /
    destination: /var/www/html/
hooks:
  BeforeInstall:    scripts/install_dependencies   timeout 300   runas root
  ApplicationStop:  scripts/stop_server            timeout 300   runas root
  ApplicationStart: scripts/start_server           timeout 300   runas root
```

`version: 0.0` is the AppSpec format version written in the file, not an application version. Provenance: **Implemented**.

No `file_exists_behavior`, no permissions block, no `ValidateService`.

## Hook scripts

`scripts/install_dependencies` (38 bytes):

```text
#!/bin/bash
sudo yum install -y httpd
```

The hook already runs as root. `sudo` is still on the command. No package version.

`scripts/start_server` (64 bytes):

```text
#!/bin/bash
sudo chmod 755 -R /var/www/html
service httpd start
```

`scripts/stop_server` (128 bytes):

```text
#!/bin/bash
isExistApp = `pgrep httpd`
if [[ -n  $isExistApp ]]; then
    service httpd stop
fi
sudo chmod 777 -R /var/www/html
```

The assignment is invalid bash because of the spaces around `=`. See `business-rules.md` BR-STOP. The backticks would have been the process check if the assignment had been valid.

`chmod 777` runs on stop. `chmod 755` runs on start, after the new files have been copied. During the stop hook the web root is the previous revision's files. Provenance: **Inferred** from CodeDeploy running `ApplicationStop` before the copy. The AppSpec does not state that order; it is the platform's hook meaning. Confirming the platform order against current AWS docs was not part of this clone.

## HTML page

`index.html`. Charset utf-8. Title `Silicon Deployment Testing`. Inline style: white text, background `#0188cc`, Arial, 14px, `h1` at 500%, `h2` at 200%. One centered `div`. No external assets.

| Tip | h1 | h2 |
|---|---|---|
| `master` | Silicon Overdrive | Master HELOOOOOOO Branch / Dev Server |
| `development` | Congratulations | Development YEAHHHHH Branch / QA Enviroment : Push |

## Configuration and auth

The script uses the default boto3 credential chain. It does not read a profile name and it does not construct a session. The README's variable names are the usual environment variables for that chain, plus the four deploy names.

| Name | Used by | Required for |
|---|---|---|
| `AWS_ACCESS_KEY_ID` | README, boto3 chain | `master` deploy |
| `AWS_SECRET_ACCESS_KEY` | README, boto3 chain | `master` deploy |
| `AWS_DEFAULT_REGION` | README. Not passed in code | `master` deploy, if boto3 has no other region |
| `APPLICATION_NAME` | key prefix and `applicationName` | `master` deploy. Missing value breaks import |
| `DEPLOYMENT_GROUP_NAME` | `deploymentGroupName` | `master` deploy |
| `DEPLOYMENT_CONFIG` | `deploymentConfigName` | `master` deploy |
| `S3_BUCKET` | `put_object` and the revision location | `master` deploy |
| `FTP_USERNAME` | `git ftp -u` | `development` deploy |
| `FTP_PASSWORD` | `git ftp -p` | `development` deploy |
| `FTP_HOST` | host in `ftp://` | `development` deploy |

No `.env` file is committed. The README tells the operator to put these in Bitbucket variables.

## Data stores

| Store | What is written | Retention in this repo |
|---|---|---|
| S3 bucket from `S3_BUCKET` | One zip per successful upload attempt, key includes a timestamp | Not deleted by the script |
| EC2 `/var/www/html` | The zip contents, replaced on each in-place deploy | AppSpec destination |
| FTP host | Whatever `git-ftp` syncs | Path not stated |
| Database | none | — |

The script does not list, expire, or delete older S3 keys.

## Security observations

These are observations from the files. They are not a pentest, and they are not a decision that the behaviour must change.

| Observation | Evidence | Provenance |
|---|---|---|
| An RSA private key is tracked at `silcodedeploy_evolvav.pem` | blob 1670 bytes, header line `BEGIN RSA PRIVATE KEY`, commit `88a609e` | Implemented. Body not copied into Silicon Ark |
| No file references that key | `git grep` for `pem` and `evolvav` | Implemented absence |
| The master zip glob picks up that file, and the AppSpec publishes `/` to the web root | yml and `appspec.yml` | Inferred |
| FTP password is an argument to `git ftp -p` | pipeline | Implemented. It will appear in the process list on the build container. The repo does not discuss that |
| README IAM attachments are full-access managed policies | `README.md` | Documented |
| Security group in the checklist is open to anywhere, including SSH and TCP 3000 | `README.md` | Documented |
| `stop_server` sets mode `777` on the web root | `scripts/stop_server` | Implemented |
| `ignoreApplicationStopFailures` is hard-coded true | `codedeploy_deploy.py` | Implemented |
| Pipeline image and boto3 pin are from 2015–2019 era | `python:3.5.7`, `boto3==1.3.0` | Implemented. Whether they still resolve is Unknown |

## Monitoring, jobs, queues

No metrics, health URL, log library, queue, cron, or worker is in the tree. The only wait is the deploy poll. Failure output is `print` to the pipeline stdout. CodeDeploy's own console is not linked from the script. The script prints `deploymentInfo` only when status is `Failed` or `Stopped`.

## Development and testing process

No test runner, linter, formatter, CI besides the two deploy pipelines, or contribution guide. The README's external links are two AWS videos/articles and one FTP video. They are references for the checklist, not tests.

Commit history is the change log: 2019-05-29 through 2019-07-26, authors Jaco Brits and siliconoverdrive, both `jaco@overdrive.co.za`.

A clone of `master` can be read offline. It cannot complete a deploy without Bitbucket variables, AWS resources from the checklist, and an instance with the CodeDeploy agent. This onboarding did not execute the pipeline.

## Files named at discovery that are not in git

The onboarding issue's top-level listing on 2026-09-28 included `sonar-project.properties` and `sonarqube_scan.bat`. Neither path is on `master` or `development`. No Sonar properties, scanner invocation, or Sonar host is in the tracked files. Provenance of those two names: the issue text. Provenance of their contents: **Unknown**.
