# PRD — CodeDeployScripts

Evidence date: 2026-10-18. Source: `git@bitbucket.org:overdrive/codedeployscripts.git`.

Default branch `master` at `06626b32e2a09034b7df2b94b9abae5809bc12e6` (2019-06-06 14:34:43 +0200, "Test master branch").

Second branch `development` at `dc915f471e87e29b745568c2a5aba62fb0e4dfd7` (2019-07-26 14:46:41 +0200, "Update Readme file"). Where the two tips differ, the requirement says so.

There is no prior PRD in Silicon Ark. Requirements below come from `README.md`, `bitbucket-pipelines.yml`, `codedeploy_deploy.py`, `appspec.yml`, the hook scripts, and `index.html`. Provenance is marked on each item.

The scan-host checkout named on the onboarding issue was not on this machine. Statements are from the clone. Placeholder secrets in the README are the characters `xxxxxxxxxx` and `yyyyyyyyyyy`. The body of `silcodedeploy_evolvav.pem` is not copied here.

## Product purpose

An operator uses this repository as a worked example of two deploy paths:

1. Pushing `master` makes Bitbucket Pipelines zip the tree, upload the zip to S3, and start an in-place AWS CodeDeploy deployment onto EC2.
2. Pushing `development` makes Bitbucket Pipelines send the tree to an FTP server with `git-ftp`.

The deployed payload's visible content is one HTML page whose heading shows which branch was deployed.

Provenance: the two paths are **Implemented** in `bitbucket-pipelines.yml`. The checklist that tells a human how to create the AWS and FTP targets is **Documented** in `README.md`. Calling the HTML page a deploy smoke banner is **Inferred** from the heading text and from the absence of any other page.

## Users and interactions

No role table, login screen, or permission check exists in this repository.

| Actor | What the tree says they do | Provenance |
|---|---|---|
| Operator | Follows the README: create IAM, S3, EC2, CodeDeploy, and Bitbucket variables, then push | **Documented** (`README.md`) |
| Bitbucket Pipelines | On `master`, installs zip and boto3, zips the tree, runs `codedeploy_deploy.py`. On `development`, installs git-ftp and runs `git ftp` | **Implemented** |
| Deploy script | Uploads `/tmp/artifact.zip` and calls `create_deployment`, then polls | **Implemented** (`codedeploy_deploy.py`) |
| CodeDeploy agent on EC2 | Runs the three hooks as root. The agent itself is installed by the README's manual `wget`, not by this repo's pipeline | **Documented** install. **Implemented** hooks |
| Browser visitor | Receives `index.html` from `httpd` after a CodeDeploy deploy. The FTP docroot is not stated | **Inferred** for the CodeDeploy path from the AppSpec destination and `service httpd start`. FTP visibility is **Unknown** |

Whether anyone besides the Bitbucket users who can push these branches may deploy is **Unknown / Requires Confirmation**. The repo has no access-control code.

## Main workflows

### W1 — Prepare AWS, once

Trigger: a person follows "Getting started AWS" in `README.md`.

The checklist, quoted as named there, is:

1. A Bitbucket account and an AWS account.
2. IAM group `CodeDeployGroup` with `AmazonEC2FullAccess`, `AmazonS3FullAccess`, and `AWSCodeDeployFullAccess`.
3. A programmatic user in that group. The README says to save the key and secret. The printed values are placeholders.
4. A role named `AWSCodeDeployRole`, trusted by `ec2.amazonaws.com` and `codedeploy.eu-west-1.amazonaws.com`, with `AWSCodeDeployRole` and `AmazonS3FullAccess` attached. The trust policy JSON is in the README.
5. S3 bucket `codedeploy`.
6. An Amazon Linux `t2.micro` instance, IAM role `AWSCodeDeployRole`, tag `name` = `CodedeployEC2Dev`, security group open for HTTP, HTTPS, SSH, and TCP 3000 from anywhere.
7. On that instance, a manual package install (the line begins `sudo sudo yum`) including PHP 5.4 from Remi, `httpd`, `git`, `ruby`, and `wget`, then the CodeDeploy agent from `https://aws-codedeploy-eu-west-1.s3.amazonaws.com/latest/install`.
8. CodeDeploy application `CodedeployDev` for EC2/On-premises, deployment group `DG1`, in-place, instances where tag `name` = `CodedeployEC2Dev`, config `CodeDeployDefault.OneAtATime`, service role `AWSCodeDeployRole`. The README says "Old Screen , Custom Development".
9. Bitbucket variables: `AWS_SECRET_ACCESS_KEY`, `AWS_ACCESS_KEY_ID`, `AWS_DEFAULT_REGION` = `eu-west-1`, `APPLICATION_NAME` = `CodedeployDev`, `DEPLOYMENT_CONFIG` = `CodeDeployDefault.OneAtATime`, `DEPLOYMENT_GROUP_NAME` = `DG1`, `S3_BUCKET` = `codedeploy`.

Provenance: **Documented**. Nothing in the pipeline creates these resources. The names above are examples in the README. The script reads the same variable names from the environment and does not hard-code `CodedeployDev`, `DG1`, or `codedeploy`. Provenance of the read: **Implemented**.

The `development` tip restyles this checklist (numbered list, fenced blocks). The names and the trust policy are the same. Provenance: **Documented** on `dc915f4`.

### W2 — Deploy `master` through CodeDeploy

Trigger: a commit on `master`. Bitbucket runs the `master` step in `bitbucket-pipelines.yml`.

1. Image `python:3.5.7`.
2. `apt-get update`, then `apt-get install -y zip`.
3. `pip install boto3==1.3.0`.
4. `zip -r /tmp/artifact.zip *`.
5. `python codedeploy_deploy.py`.

The script then:

1. Builds an object key `{APPLICATION_NAME}/{YYYYmmddHHMMSS}-bitbucket_builds.zip` at import time.
2. `put_object` of `/tmp/artifact.zip` into `S3_BUCKET` at that key.
3. `create_deployment` with revision type `S3`, bundle type `zip`, description `New deployment from BitBucket`, and `ignoreApplicationStopFailures=True`.
4. Polls `get_deployment` until status is `Succeeded` (exit 0, prints "Deployment Succeeded"), or `Failed` or `Stopped` (exit 1, prints the deployment info). `InProgress`, `Queued`, and `Created` keep polling.

Provenance: **Implemented**.

On the instance, CodeDeploy applies `appspec.yml`:

- Files: source `/` to destination `/var/www/html/`.
- `ApplicationStop`: `scripts/stop_server`, timeout 300 seconds, `runas: root`.
- `BeforeInstall`: `scripts/install_dependencies`, timeout 300 seconds, `runas: root`.
- `ApplicationStart`: `scripts/start_server`, timeout 300 seconds, `runas: root`.

Provenance: **Implemented**.

Expected visible result after a successful start hook: `httpd` serving `index.html`. On `master` the headings are "Silicon Overdrive" and "Master HELOOOOOOO Branch / Dev Server". Provenance: **Implemented** page text. That `httpd` is what a visitor hits is **Inferred**.

### W3 — Deploy `development` through FTP

Trigger: a commit on `development`. Bitbucket runs the `development` step.

1. Same image `python:3.5.7`.
2. `apt-get update`, then `apt-get -qq install zip git git-ftp`.
3. On the `development` tip: `git ftp push -u "$FTP_USERNAME" -p "$FTP_PASSWORD" ftp://$FTP_HOST`. The init line is commented.
4. On the `master` tip of this same file, the opposite is true: init is active and push is commented. That step runs only if the pipeline file that contains it is the one on the branch being built.

README FTP section:

- Variables `FTP_USERNAME`, `FTP_PASSWORD`, `FTP_HOST`.
- "On first commit / deploy via FTP use the command with git init."
- "After that use command git push, all is in bitbucket-pipelines.yml."

Provenance: variables and those two sentences are **Documented**. The commands in the yml are `git ftp init` and `git ftp push`, not `git init` and `git push`. That wording gap is **Documented** versus **Implemented**.

Expected visible result is not stated for FTP. The `development` page headings are "Congratulations" and "Development YEAHHHHH Branch / QA Enviroment : Push". The spelling "Enviroment" is the file's text. Provenance: **Implemented**. Which directory on the FTP host receives the files is **Unknown**. The URL form is `ftp://$FTP_HOST` with no path.

### W4 — What the visitor sees

Trigger: an HTTP request to the host that serves `/var/www/html` after W2.

`index.html` is a single centered page. Title "Silicon Deployment Testing". White text, background `#0188cc`, Arial. No form, no script, no stylesheet file. Provenance: **Implemented**.

The page does not say which server it is on. The `master` subtitle says both "Master" and "Dev Server". The `development` subtitle says "QA Enviroment". Provenance: **Documented** by the heading strings. A rule that `master` is production was **not found** on the current `master` tip. An earlier `master` heading, replaced on 2019-06-02, did say "Master Branch / Production Deployment".

## Functional requirements

### FR-001 — Two branch pipelines

The pipeline file defines `pipelines.branches.master` and `pipelines.branches.development`. It defines no default pipeline and no pull-request pipeline.

| | |
|---|---|
| Who | Bitbucket Pipelines |
| Trigger | A build of that branch |
| Inputs | The yml on the branch being built |
| Behaviour | Only the matching branch key runs |
| Output | The step's shell transcript |
| Validation | None in the repo |
| Errors | A non-zero command fails the step. The yml has no `max-time` and no retry |
| Evidence | `bitbucket-pipelines.yml` |
| Implementation | Implemented |
| Confidence | High |

### FR-002 — Master step packages the work tree

The `master` step installs `zip` and `boto3==1.3.0`, writes `/tmp/artifact.zip` from `*`, and runs `python codedeploy_deploy.py`.

`*` does not include dotfiles. It does include every other tracked file at the root and in `scripts/`, including `silcodedeploy_evolvav.pem`, `codedeploy_deploy.py`, and `bitbucket-pipelines.yml`.

| | |
|---|---|
| Evidence | `bitbucket-pipelines.yml` `master` step; `git ls-files` |
| Implementation | Implemented. Which names `*` expands to is **Inferred** from the shell glob and the file list |
| Confidence | High |

### FR-003 — Upload the zip to S3

`upload_to_s3` creates an S3 client and calls `put_object` with body `/tmp/artifact.zip`, bucket `S3_BUCKET`, and key `BUCKET_KEY`.

`BUCKET_KEY` is `{APPLICATION_NAME}/{YYYYmmddHHMMSS}-bitbucket_builds.zip`. The timestamp is local time from `strftime("%Y%m%d%H%M%S")`, taken when the module is imported.

If `APPLICATION_NAME` is missing, that concatenation runs at import, before `main`. The file does not test the variable. Provenance: **Implemented** expression. The Python result of `None + str` is **Inferred** (TypeError). The process then never reaches `main`.

| | |
|---|---|
| Errors | `ClientError` while creating the client or putting the object prints a message and returns False. `main` then `sys.exit(1)`. `IOError` while opening the zip does the same. Other exceptions are not caught |
| Evidence | `codedeploy_deploy.py` `upload_to_s3`, `main` |
| Implementation | Implemented |
| Confidence | High |

### FR-004 — Start a CodeDeploy revision and wait

`deploy_new_revision` creates a CodeDeploy client and calls `create_deployment`.

Inputs from the environment, all passed through `str(...)` except the bucket on the S3 location, which is passed as `os.getenv('S3_BUCKET')` directly:

- `applicationName` = `APPLICATION_NAME`
- `deploymentGroupName` = `DEPLOYMENT_GROUP_NAME`
- `deploymentConfigName` = `DEPLOYMENT_CONFIG`
- revision bucket = `S3_BUCKET`, key = `BUCKET_KEY`, `bundleType` = `zip`
- `description` = `New deployment from BitBucket`
- `ignoreApplicationStopFailures` = `True`

The wait loop calls `get_deployment` with no `sleep`, even though `sleep` is imported. There is no attempt limit and no deadline in the script.

| Status | Result |
|---|---|
| `Succeeded` | Print "Deployment Succeeded", return True, process exits 0 |
| `Failed` or `Stopped` | Print "Deployment Failed" and the deployment info, return False, `sys.exit(1)` |
| `InProgress`, `Queued`, `Created` | Loop again |
| Any other status | The `if` chain does not return. The loop continues |
| `ClientError` | Print the error, return False, `sys.exit(1)` |

| | |
|---|---|
| Evidence | `codedeploy_deploy.py` `deploy_new_revision` |
| Implementation | Implemented |
| Confidence | High |

A missing region is not handled by name. Client construction catches only `ClientError`. The README lists `AWS_DEFAULT_REGION`. The script never passes `region_name`. Provenance: **Implemented** catch list. The failure mode when the region is unset is **Unknown** from this file.

### FR-005 — Publish the bundle to the web root

The AppSpec copies source `/` to `/var/www/html/` on `os: linux`.

| | |
|---|---|
| Evidence | `appspec.yml` |
| Implementation | Implemented |
| Confidence | High |

There is no `ValidateService` hook, no `AfterInstall` hook, and no file exclusion. Provenance: **Implemented** by absence from the AppSpec.

### FR-006 — Hooks around the copy

| Hook | Script | What the script contains | Timeout | User |
|---|---|---|---|---|
| `ApplicationStop` | `scripts/stop_server` | See FR-007 | 300 | root |
| `BeforeInstall` | `scripts/install_dependencies` | `sudo yum install -y httpd` | 300 | root |
| `ApplicationStart` | `scripts/start_server` | `sudo chmod 755 -R /var/www/html` then `service httpd start` | 300 | root |

All three files are mode `100644` in git and start with `#!/bin/bash`. Provenance: **Implemented**.

`install_dependencies` does not pin an `httpd` version and does not enable the service across reboot. `start_server` does not check whether `httpd` was already running.

The deployment sets `ignoreApplicationStopFailures=True`, so a non-zero `ApplicationStop` does not fail the deployment by itself. Provenance: **Implemented** in `create_deployment`.

### FR-007 — Stop script text

`scripts/stop_server` is:

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

The spaces around `=` mean this is not an assignment. Provenance: **Implemented** text.

Inferred shell result, because the repo has no test: the first line runs a command named `isExistApp`. With no `set -e`, a missing command does not abort the script. `isExistApp` is then unset, the `if` is false, `httpd` is not stopped, and `chmod 777` still runs. If `chmod` returns 0, the hook returns 0. Confidence of that reading: medium. It needs a shell to confirm.

The README does not mention mode `777` or mode `755`.

### FR-008 — Smoke page content

`index.html` is the only HTML file.

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

The `<title>` on both tips is "Silicon Deployment Testing". Provenance: **Implemented**.

### FR-009 — FTP credentials and command

The `development` step reads `FTP_USERNAME`, `FTP_PASSWORD`, and `FTP_HOST`. The password is passed on the `git ftp` command line with `-p`. The README marks `FTP_PASSWORD` with the comment `#secure`. It does not mark `FTP_USERNAME` that way.

Empty values are not checked in the yml. Provenance: **Implemented**.

Which of init or push is the active line depends on the branch. See W3.

### FR-010 — Manual EC2 bootstrap stays manual

The PHP package line, the CodeDeploy agent install, the security group, and the instance tag are README steps. No pipeline step and no hook runs them. The hooks assume `yum` and `service` exist, which matches the README's Amazon Linux choice. Provenance: **Documented** versus **Implemented**.

## Integrations

| System | Purpose | Trigger | Direction | Auth as written | Failure behaviour |
|---|---|---|---|---|---|
| Bitbucket Pipelines | Run the branch step | Push to `master` or `development` | Bitbucket reads the repo and runs the image | Pipeline environment variables | Non-zero shell exits the step. No retry in the yml |
| Amazon S3 | Store the zip | `put_object` after a `master` build | This repo writes one object | boto3 default chain. README names `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` | Caught `ClientError` or `IOError` exits 1 |
| AWS CodeDeploy | In-place deploy to the group | `create_deployment` | This repo calls the API; the agent on EC2 pulls the revision | Same boto3 chain. README service role `AWSCodeDeployRole` | `Failed` and `Stopped` exit 1. The poll has no deadline |
| Amazon EC2 | Run `httpd` and the hooks | CodeDeploy targets the tag in the group | Agent runs hooks as root | Instance role from the README | Not handled in this repo beyond the hook exit codes |
| FTP host | Receive the tree | `development` pipeline | `git ftp` to `ftp://$FTP_HOST` | `FTP_USERNAME` / `FTP_PASSWORD` on the command line | Not handled beyond `git ftp`'s own exit code |
| AWS sample project | Stated source of the basic scripts | README link | Read-only reference | none | n/a |

No webhook, queue, mailer, or database client is in the tree.

## Constraints

- Pipeline image is pinned to `python:3.5.7`. boto3 is pinned to `1.3.0` on the `master` step only. Provenance: **Implemented**.
- AppSpec `os` is `linux`. Hook timeouts are 300 seconds. Provenance: **Implemented**.
- Deployment config name comes from `DEPLOYMENT_CONFIG`. The README example is `CodeDeployDefault.OneAtATime`. The script does not check the value. Provenance: **Documented** example, **Implemented** pass-through.
- Region is not a parameter of the script. The README example is `eu-west-1`, and the agent URL and the role trust policy are specific to `eu-west-1`. Provenance: **Documented**.
- The zip is rebuilt on every `master` build. The object key includes a timestamp, so a new build does not overwrite the previous key. Provenance: **Implemented**.
- `ignoreApplicationStopFailures` is hard-coded `True`. Provenance: **Implemented**.

## Assumptions the tree does not confirm

| Topic | What would be easy to assume | What the tree actually supports |
|---|---|---|
| `master` is production | The current subtitle says "Dev Server" | No environment matrix. An older heading said "Production Deployment" and was replaced |
| `development` is QA | The subtitle says "QA Enviroment" | That word is the banner. No separate config file |
| Port 3000 is the app port | The security group opens it | `httpd` is started with no port flag. Nothing else listens |
| PHP is required | The manual install includes PHP 5.4 | No PHP file is committed |
| The pem is a deploy key | The filename contains "codedeploy" | No script, yml, or README line names the file |
| Sonar is part of this repo | The 2026-09-28 scan listing included two Sonar files | Those paths are not in either tip |

## Acceptance notes for a tester

A tester can derive these checks from the files without a live AWS account:

- `master` yml contains `zip -r /tmp/artifact.zip *` and `python codedeploy_deploy.py`, and installs `boto3==1.3.0`.
- `development` tip yml runs `git ftp push` and comments `git ftp init`. `master` tip yml does the opposite.
- `codedeploy_deploy.py` exits 1 when upload or create/poll returns False, and treats only `Succeeded` as success.
- `appspec.yml` destination is `/var/www/html/` and the three hook paths match the files under `scripts/`.
- `index.html` headings match the table in FR-008.
- `git grep` of `pem` and `evolvav` outside the key file itself returns no hits.

Checks that need a live account are the human-confirmation list in `project-plan.md`.
