Skip to content

Commit ea95f3f

Browse files
Initial installer version (#110)
Initial installer version
1 parent e3b7fbe commit ea95f3f

File tree

13 files changed

+973
-4
lines changed

13 files changed

+973
-4
lines changed

.circleci/config.yml

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,34 @@ jobs:
108108
MAKE_JOB_COUNT: 8
109109
WAF: "<< parameters.waf >>"
110110
NGINX_VERSION: "<< parameters.nginx-version >>"
111+
build_installer_arm64:
112+
steps:
113+
- checkout
114+
- run: go -C ./installer/configurator build -o nginx-configurator
115+
- persist_to_workspace:
116+
root: "./installer/configurator"
117+
paths:
118+
- "nginx-configurator"
119+
- store_artifacts:
120+
path: "./installer/configurator/nginx-configurator"
121+
destination: nginx-configurator
122+
machine:
123+
image: ubuntu-2204:current
124+
resource_class: arm.medium
125+
build_installer_amd64:
126+
steps:
127+
- checkout
128+
- run: go -C ./installer/configurator build -o nginx-configurator
129+
- persist_to_workspace:
130+
root: "./installer/configurator"
131+
paths:
132+
- "nginx-configurator"
133+
- store_artifacts:
134+
path: "./installer/configurator/nginx-configurator"
135+
destination: nginx-configurator
136+
machine:
137+
image: ubuntu-2204:current
138+
resource_class: medium
111139
coverage:
112140
environment:
113141
DOCKER_BUILDKIT: 1
@@ -206,7 +234,7 @@ jobs:
206234
entrypoint: "/bin/sh"
207235
steps:
208236
- checkout
209-
- run: find bin/ test/ example/ -type f -executable | xargs shellcheck --exclude
237+
- run: find bin/ test/ example/ installer/ -type f -executable | xargs shellcheck --exclude
210238
SC1071,SC1091,SC2317
211239
workflows:
212240
build-and-test:
@@ -241,6 +269,10 @@ workflows:
241269
- 'ON'
242270
- 'OFF'
243271
name: build << matrix.nginx-version >> on arm64 WAF << matrix.waf >>
272+
- build_installer_amd64:
273+
name: build installer on amd64
274+
- build_installer_arm64:
275+
name: build installer on arm64
244276
- coverage:
245277
name: Coverage on 1.27.0 with WAF ON
246278
- test:
@@ -359,6 +391,10 @@ workflows:
359391
- 'ON'
360392
- 'OFF'
361393
name: build << matrix.nginx-version >> on arm64 WAF << matrix.waf >>
394+
- build_installer_amd64:
395+
name: build installer on amd64
396+
- build_installer_arm64:
397+
name: build installer on arm64
362398
- test:
363399
matrix:
364400
parameters:

bin/release.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,29 @@ def sign_package(package_path: str) -> None:
142142
run(command, check=True)
143143

144144

145+
def prepare_installer_release_artifact(work_dir, build_job_number, arch):
146+
artifacts = send_ci_request_paged(
147+
f"/project/{PROJECT_SLUG}/{build_job_number}/artifacts")
148+
module_url = None
149+
for artifact in artifacts:
150+
name = artifact["path"]
151+
if name == "nginx-configurator":
152+
module_url = artifact["url"]
153+
154+
if module_url is None:
155+
raise Exception(
156+
f"Job number {build_job_number} doesn't have an 'nginx-configurator' build artifact."
157+
)
158+
159+
module_path = work_dir / "nginx-configurator"
160+
download_file(module_url, module_path)
161+
162+
# Package and sign
163+
tarball_path = (work_dir / f"nginx-configurator-{arch}.tgz")
164+
package(module_path, out=tarball_path)
165+
sign_package(tarball_path)
166+
167+
145168
def prepare_release_artifact(work_dir, build_job_number, version, arch, waf):
146169
waf_suffix = "-appsec" if waf else ""
147170
artifacts = send_ci_request_paged(
@@ -185,10 +208,19 @@ def prepare_release_artifact(work_dir, build_job_number, version, arch, waf):
185208
sign_package(debug_tarball_path)
186209

187210

188-
def handle_job(job, work_dir):
211+
def handle_job(job, work_dir, installer):
212+
189213
# See the response schema for a list of statuses:
190214
# https://circleci.com/docs/api/v2/index.html#operation/listWorkflowJobs
191-
if job["name"].startswith("build "):
215+
if installer and job["name"].startswith("build installer "):
216+
# name should be something like "build installer on arm64"
217+
match = re.match(r"build installer on (amd64|arm64)", job["name"])
218+
if match is None:
219+
raise Exception(f'Job name does not match regex "{re}": {job}')
220+
arch = match.groups()[0]
221+
prepare_installer_release_artifact(work_dir, job["job_number"], arch)
222+
if not installer and job["name"].startswith(
223+
"build ") and not job["name"].startswith("build installer "):
192224
# name should be something like "build 1.25.4 on arm64 WAF ON"
193225
match = re.match(r"build ([\d.]+) on (amd64|arm64) WAF (ON|OFF)",
194226
job["name"])
@@ -213,6 +245,9 @@ def handle_job(job, work_dir):
213245
help=
214246
"ID of the release workflow. Find in job url. Example: https://app.circleci.com/pipelines/github/DataDog/nginx-datadog/542/workflows/<WORKFLOW_ID>",
215247
)
248+
parser.add_argument("--installer",
249+
help="Release the NGINX installer",
250+
action=argparse.BooleanOptionalAction)
216251
options = parser.parse_args()
217252

218253
ci_api_token = options.ci_token
@@ -247,7 +282,7 @@ def handle_job(job, work_dir):
247282
sys.exit(1)
248283

249284
for job in jobs:
250-
result = handle_job(job, Path(work_dir))
285+
result = handle_job(job, Path(work_dir), options.installer)
251286
if result != "done":
252287
sys.exit(1)
253288

installer/README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
NGINX INSTALLER
2+
===============
3+
This folder contains the necessary components to install the NGINX module.
4+
It consists of an sh script, plus a go binary (configurator) that performs the actual installation.
5+
Running this installer results in the NGINX module being installed locally.
6+
7+
Shell script
8+
------------
9+
Verifies connectivity to the Datadog Agent. Detects the architecture, downloads
10+
the Configurator for the specific architecture and invokes it forwarding all the
11+
parameters plus the architecture.
12+
13+
Configurator
14+
------------
15+
Does the bulk of the installation.
16+
1. Validates parameters
17+
2. Validates NGINX installation and version
18+
3. Downloads the NGINX module
19+
4. Makes neccessary config modifications
20+
5. Validates the modified configurations.
21+
22+
Local Testing
23+
-------------
24+
1. Compile the go binary
25+
```bash
26+
env GOOS=linux go -C configurator build -o ../nginx-configurator
27+
```
28+
2. Start docker compose with the docker-compose file provided. It boots up an NGINX
29+
instance and a Datadog Agent instance with connectivity to each other.
30+
```bash
31+
DD_API_KEY=<YOUR_API_KEY> docker compose -f test/docker-compose.yml up -d
32+
```
33+
3. Run the installer
34+
```bash
35+
docker compose -f test/docker-compose.yml exec nginx bash -c "cd /installer && sh install-nginx-datadog.sh --appId 123 --site datadoghq.com --clientToken abcdef --sessionSampleRate 50 --sessionReplaySampleRate 50 --agentUri http://datadog-agent:8126"
36+
```
37+
38+
Currently, the latest release doesn't yet support rum injection, so expect a message
39+
showing an error when validating the final NGINX configuration `unknown directive
40+
"datadog_rum" in /etc/nginx/nginx.conf`
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"os"
7+
8+
log "github.com/sirupsen/logrus"
9+
)
10+
11+
var InstallerVersion = "0.1.0"
12+
13+
func validateInput(appID, site, clientToken, arch string, sessionSampleRate, sessionReplaySampleRate int) error {
14+
15+
log.Debug("Validating input arguments")
16+
17+
if appID == "" {
18+
return NewInstallerError(ArgumentError, fmt.Errorf("--appId is required"))
19+
}
20+
if site == "" {
21+
return NewInstallerError(ArgumentError, fmt.Errorf("--site is required"))
22+
}
23+
if clientToken == "" {
24+
return NewInstallerError(ArgumentError, fmt.Errorf("--clientToken is required"))
25+
}
26+
if sessionSampleRate < 0 || sessionSampleRate > 100 {
27+
return NewInstallerError(ArgumentError, fmt.Errorf("sessionSampleRate is required and must be between 0 and 100"))
28+
}
29+
if sessionReplaySampleRate < 0 || sessionReplaySampleRate > 100 {
30+
return NewInstallerError(ArgumentError, fmt.Errorf("sessionReplaySampleRate is required and must be between 0 and 100"))
31+
}
32+
if arch != "amd64" && arch != "arm64" {
33+
return NewInstallerError(ArgumentError, fmt.Errorf("arch must be either 'amd64' or 'arm64'"))
34+
}
35+
return nil
36+
}
37+
38+
func handleError(err error) {
39+
// TODO: Send telemetry
40+
41+
log.Error(err)
42+
43+
os.Exit(1)
44+
}
45+
46+
func main() {
47+
appID := flag.String("appId", "", "Application ID")
48+
site := flag.String("site", "", "Site")
49+
clientToken := flag.String("clientToken", "", "Client Token")
50+
sessionSampleRate := flag.Int("sessionSampleRate", -1, "Session Sample Rate (0-100)")
51+
sessionReplaySampleRate := flag.Int("sessionReplaySampleRate", -1, "Session Replay Sample Rate (0-100)")
52+
arch := flag.String("arch", "", "Architecture (amd64 or arm64)")
53+
agentUri := flag.String("agentUri", "http://localhost:8126", "Datadog Agent URI")
54+
skipVerify := flag.Bool("skipVerify", false, "Skip verifying downloads")
55+
verbose := flag.Bool("verbose", false, "Verbose output")
56+
dryRun := flag.Bool("dryRun", false, "Dry run (no changes made)")
57+
58+
flag.Parse()
59+
60+
if *verbose {
61+
log.SetLevel(log.DebugLevel)
62+
log.Debug("Verbose output enabled")
63+
} else {
64+
log.SetLevel(log.InfoLevel)
65+
}
66+
67+
log.Info("Starting installer version ", InstallerVersion)
68+
69+
if *dryRun {
70+
log.Info("Dry run enabled. No changes will be made.")
71+
}
72+
73+
if err := validateInput(*appID, *site, *clientToken, *arch, *sessionSampleRate, *sessionReplaySampleRate); err != nil {
74+
handleError(err)
75+
}
76+
77+
var configurator ProxyConfigurator = &NginxConfigurator{}
78+
79+
if err := configurator.VerifyRequirements(); err != nil {
80+
handleError(err)
81+
}
82+
83+
if err := configurator.DownloadAndInstallModule(*arch, *skipVerify); err != nil {
84+
handleError(err)
85+
}
86+
87+
if err := configurator.ModifyConfig(*appID, *site, *clientToken, *agentUri, *sessionSampleRate, *sessionReplaySampleRate, *dryRun); err != nil {
88+
handleError(err)
89+
}
90+
91+
if !*dryRun {
92+
if err := configurator.ValidateConfig(); err != nil {
93+
handleError(err)
94+
}
95+
96+
log.Info("Datadog NGINX module has been successfully installed and configured. Please restart NGINX for the changes to take effect")
97+
}
98+
}

installer/configurator/error.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package main
2+
3+
import "fmt"
4+
5+
type ErrorType int
6+
7+
const (
8+
UnexpectedError ErrorType = iota
9+
ArgumentError
10+
InternalError
11+
NginxError
12+
TelemetryError
13+
)
14+
15+
func (e ErrorType) String() string {
16+
switch e {
17+
case UnexpectedError:
18+
return "UnexpectedError"
19+
case ArgumentError:
20+
return "ArgumentError"
21+
case InternalError:
22+
return "InternalError"
23+
case NginxError:
24+
return "NginxError"
25+
case TelemetryError:
26+
return "TelemetryError"
27+
default:
28+
return fmt.Sprintf("%d", int(e))
29+
}
30+
}
31+
32+
func NewInstallerError(errorType ErrorType, err error) *InstallerError {
33+
return &InstallerError{
34+
ErrorType: errorType,
35+
Err: err,
36+
}
37+
}
38+
39+
type InstallerError struct {
40+
ErrorType ErrorType
41+
Err error
42+
}
43+
44+
func (m *InstallerError) Error() string {
45+
return fmt.Sprintf("%s: %v", m.ErrorType, m.Err)
46+
}

installer/configurator/go.mod

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
module github.com/DataDog/nginx-datadog/installer
2+
3+
go 1.21.0
4+
5+
require (
6+
github.com/google/go-github v17.0.0+incompatible
7+
github.com/google/uuid v1.6.0
8+
github.com/sirupsen/logrus v1.9.3
9+
)
10+
11+
require (
12+
github.com/google/go-querystring v1.1.0 // indirect
13+
github.com/stretchr/testify v1.9.0 // indirect
14+
golang.org/x/sys v0.22.0 // indirect
15+
)

installer/configurator/go.sum

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4+
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
5+
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
6+
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
7+
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
8+
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
9+
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
10+
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
11+
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
12+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
13+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
14+
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
15+
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
16+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
17+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
18+
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
19+
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
20+
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
21+
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
22+
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
23+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
24+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
25+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
26+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
27+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)