Skip to content

Commit 6f17492

Browse files
Merge branch 'akitasoftware:main' into main
2 parents 2629e7a + 3db5466 commit 6f17492

File tree

6 files changed

+396
-0
lines changed

6 files changed

+396
-0
lines changed

cmd/internal/ec2/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
### Amazon EC2/ Linux Server
2+
3+
### Introduction
4+
5+
- The Postman Live Collection Agent (LCA) runs as a systemd service on your server
6+
- The Postman collection is populated with endpoints observed from the traffic arriving at your service.
7+
8+
### Prerequisites
9+
10+
- Your server's OS supports `systemd`
11+
- `root` user
12+
13+
### Usage
14+
15+
- Log in as root user, or use `sudo su` to enable root before running the below command
16+
```
17+
POSTMAN_API_KEY=<postman-api-key> postman-lc-agent setup --collection <postman-collectionID>
18+
```
19+
20+
To check the status or logs please use
21+
22+
```
23+
journalctl -fu postman-lc-agent
24+
```
25+
26+
#### Why is root required?
27+
28+
- To enable and configure the agent as a systemd services
29+
- Env Configuration file location `/etc/default/postman-lc-agent`
30+
- Systemd service file location `/usr/lib/systemd/system/postman-lc-agent.service`
31+
32+
### Uninstall
33+
34+
- You can disable the systemd service using
35+
36+
`sudo systemctl disable --now postman-lc-agent`

cmd/internal/ec2/add.go

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
package ec2
2+
3+
import (
4+
"embed"
5+
"os"
6+
"os/exec"
7+
"os/user"
8+
"strings"
9+
"text/template"
10+
11+
"github.com/AlecAivazis/survey/v2"
12+
"github.com/akitasoftware/akita-cli/printer"
13+
"github.com/akitasoftware/akita-cli/telemetry"
14+
"github.com/pkg/errors"
15+
)
16+
17+
const (
18+
envFileName = "postman-lc-agent"
19+
envFileTemplateName = "postman-lc-agent.tmpl"
20+
envFileBasePath = "/etc/default/"
21+
envFilePath = envFileBasePath + envFileName
22+
23+
serviceFileName = "postman-lc-agent.service"
24+
serviceFileBasePath = "/usr/lib/systemd/system/"
25+
serviceFilePath = serviceFileBasePath + serviceFileName
26+
27+
// Output of command: systemctl is-enabled postman-lc-agent
28+
// Refer: https://www.freedesktop.org/software/systemd/man/latest/systemctl.html#Exit%20status
29+
enabled = "enabled" // exit code: 0
30+
disabled = "disabled" // exit code: 1
31+
nonExisting = "Failed to get unit file state for postman-lc-agent.service: No such file or directory" // exit code: 1
32+
)
33+
34+
// Embed files inside the binary. Requires Go >=1.16
35+
36+
//go:embed postman-lc-agent.service
37+
var serviceFile string
38+
39+
// FS is used for easier template parsing
40+
41+
//go:embed postman-lc-agent.tmpl
42+
var envFileFS embed.FS
43+
44+
// Helper function for reporting telemetry
45+
func reportStep(stepName string) {
46+
telemetry.WorkflowStep("Starting systemd conguration", stepName)
47+
}
48+
49+
func setupAgentForServer(collectionId string) error {
50+
51+
err := checkUserPermissions()
52+
if err != nil {
53+
return err
54+
}
55+
err = checkSystemdExists()
56+
if err != nil {
57+
return err
58+
}
59+
60+
err = configureSystemdFiles(collectionId)
61+
if err != nil {
62+
return err
63+
}
64+
65+
err = enablePostmanAgent()
66+
if err != nil {
67+
return err
68+
}
69+
70+
return nil
71+
}
72+
73+
func askToReconfigure() error {
74+
var isReconfigure bool
75+
76+
printer.Infof("postman-lc-agent is already present as a systemd service\n")
77+
printer.Infof("Helpful commands \n Check status: systemctl status postman-lc-agent \n Disable agent: systemctl disable --now postman-lc-agent \n Check Logs: journalctl -fu postman-lc-agent\n Check env file: cat %s \n Check systemd service file: cat %s \n", envFilePath, serviceFilePath)
78+
79+
err := survey.AskOne(
80+
&survey.Confirm{
81+
Message: "Overwrite old API key and Collection ID values in systemd configuration file with current values?",
82+
Default: true,
83+
Help: "Any edits made to systemd configuration files will be over-written.",
84+
},
85+
&isReconfigure,
86+
)
87+
if !isReconfigure {
88+
printer.Infof("Exiting setup \n")
89+
os.Exit(0)
90+
return nil
91+
}
92+
if err != nil {
93+
return errors.Wrap(err, "failed to run reconfiguration prompt")
94+
}
95+
return nil
96+
}
97+
98+
// Check is systemd service already exists
99+
func checkReconfiguration() error {
100+
101+
cmd := exec.Command("systemctl", []string{"is-enabled", "postman-lc-agent"}...)
102+
out, err := cmd.CombinedOutput()
103+
104+
if err != nil {
105+
if exitError, ok := err.(*exec.ExitError); ok {
106+
exitCode := exitError.ExitCode()
107+
if exitCode != 1 {
108+
return errors.Wrapf(err, "Received non 1 exitcode for systemctl is-enabled. \n Command output:%s \n Please send this log message to [email protected] for assistance\n", out)
109+
}
110+
if strings.Contains(string(out), disabled) {
111+
return askToReconfigure()
112+
} else if strings.Contains(string(out), nonExisting) {
113+
return nil
114+
}
115+
}
116+
return errors.Wrapf(err, "failed to run systemctl is-enabled posman-lc-agent")
117+
}
118+
if strings.Contains(string(out), enabled) {
119+
return askToReconfigure()
120+
}
121+
return errors.Errorf("The systemctl is-enabled command produced output this tool doesn't recognize: %q. \n Please send this log message to [email protected] for assistance\n", string(out))
122+
123+
}
124+
125+
func checkUserPermissions() error {
126+
127+
// Exact permissions required are
128+
// read/write permissions on /etc/default/postman-lc-agent
129+
// read/write permission on /usr/lib/system/systemd
130+
// enable, daemon-reload, start, stop permission for systemctl
131+
132+
printer.Infof("Checking user permissions \n")
133+
cu, err := user.Current()
134+
if err != nil {
135+
return errors.Wrapf(err, "could not get current user")
136+
}
137+
if !strings.EqualFold(cu.Name, "root") {
138+
printer.Errorf("root user is required to setup systemd service and edit related files.\n")
139+
return errors.Errorf("Please run the command again with root user")
140+
}
141+
return nil
142+
}
143+
144+
func checkSystemdExists() error {
145+
message := "Checking if systemd exists"
146+
printer.Infof(message + "\n")
147+
reportStep(message)
148+
149+
_, serr := exec.LookPath("systemctl")
150+
if serr != nil {
151+
printer.Errorf("We don't have support for non-systemd OS as of now.\n For more information please contact [email protected].\n")
152+
return errors.Errorf("Could not find systemd binary in your OS.")
153+
}
154+
return nil
155+
}
156+
157+
func configureSystemdFiles(collectionId string) error {
158+
message := "Configuring systemd files"
159+
printer.Infof(message + "\n")
160+
reportStep(message)
161+
162+
err := checkReconfiguration()
163+
if err != nil {
164+
return err
165+
}
166+
167+
// Write collectionId and postman-api-key to go template file
168+
169+
tmpl, err := template.ParseFS(envFileFS, envFileTemplateName)
170+
if err != nil {
171+
return errors.Wrapf(err, "systemd env file parsing failed")
172+
}
173+
174+
data := struct {
175+
PostmanAPIKey string
176+
CollectionId string
177+
}{
178+
PostmanAPIKey: os.Getenv("POSTMAN_API_KEY"),
179+
CollectionId: collectionId,
180+
}
181+
182+
// Ensure /etc/default exists
183+
cmd := exec.Command("mkdir", []string{"-p", envFileBasePath}...)
184+
_, err = cmd.CombinedOutput()
185+
if err != nil {
186+
return errors.Wrapf(err, "failed to create %s directory\n", envFileBasePath)
187+
}
188+
189+
envFile, err := os.Create(envFilePath)
190+
if err != nil {
191+
printer.Errorf("Failed to create systemd env file")
192+
return err
193+
}
194+
195+
err = tmpl.Execute(envFile, data)
196+
if err != nil {
197+
printer.Errorf("Failed to write values to systemd env file")
198+
return err
199+
}
200+
201+
// Ensure /usr/lib/systemd/system exists
202+
cmd = exec.Command("mkdir", []string{"-p", serviceFileBasePath}...)
203+
_, err = cmd.CombinedOutput()
204+
if err != nil {
205+
return errors.Wrapf(err, "failed to create %s directory", serviceFileBasePath)
206+
}
207+
208+
err = os.WriteFile(serviceFilePath, []byte(serviceFile), 0600)
209+
if err != nil {
210+
printer.Errorf("failed to create %s file in %s directory with err %q \n", serviceFileName, serviceFilePath, err)
211+
return err
212+
}
213+
214+
return nil
215+
}
216+
217+
// Starts the postman LCA agent as a systemd service
218+
func enablePostmanAgent() error {
219+
message := "Enabling postman-lc-agent as a service"
220+
reportStep(message)
221+
printer.Infof(message + "\n")
222+
223+
cmd := exec.Command("systemctl", []string{"daemon-reload"}...)
224+
_, err := cmd.CombinedOutput()
225+
if err != nil {
226+
return errors.Wrapf(err, "failed to run systemctl daemon-reload")
227+
}
228+
// systemctl start postman-lc-service
229+
cmd = exec.Command("systemctl", []string{"enable", "--now", serviceFileName}...)
230+
_, err = cmd.CombinedOutput()
231+
if err != nil {
232+
return errors.Wrapf(err, "faild to run systemctl enable --now")
233+
}
234+
printer.Infof("Postman LC Agent enabled as a systemd service. Please check logs using the below command \n")
235+
printer.Infof("journalctl -fu postman-lc-agent \n")
236+
237+
return nil
238+
}

cmd/internal/ec2/ec2.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package ec2
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/akitasoftware/akita-cli/cmd/internal/cmderr"
7+
"github.com/akitasoftware/akita-cli/rest"
8+
"github.com/akitasoftware/akita-cli/telemetry"
9+
"github.com/akitasoftware/akita-cli/util"
10+
"github.com/pkg/errors"
11+
"github.com/spf13/cobra"
12+
)
13+
14+
var (
15+
// Mandatory flag: Postman collection id
16+
collectionId string
17+
)
18+
19+
var Cmd = &cobra.Command{
20+
Use: "setup",
21+
Short: "Add the Postman Live Collections Agent to the current server.",
22+
Long: "The CLI will add the Postman Live Collections Agent as a systemd service to your current server.",
23+
SilenceUsage: true,
24+
RunE: addAgentToEC2,
25+
}
26+
27+
var RemoveFromEC2Cmd = &cobra.Command{
28+
Use: "remove",
29+
Short: "Remove the Postman Live Collections Agent from EC2.",
30+
Long: "Remove a previously installed Postman agent from an EC2 server.",
31+
SilenceUsage: true,
32+
RunE: removeAgentFromEC2,
33+
34+
// Temporarily hide from users until complete
35+
Hidden: true,
36+
}
37+
38+
func init() {
39+
Cmd.PersistentFlags().StringVar(&collectionId, "collection", "", "Your Postman collection ID")
40+
Cmd.MarkPersistentFlagRequired("collection")
41+
42+
Cmd.AddCommand(RemoveFromEC2Cmd)
43+
}
44+
45+
func addAgentToEC2(cmd *cobra.Command, args []string) error {
46+
// Check for API key
47+
_, err := cmderr.RequirePostmanAPICredentials("The Postman Live Collections Agent must have an API key in order to capture traces.")
48+
if err != nil {
49+
return err
50+
}
51+
52+
// Check collecton Id's existence
53+
if collectionId == "" {
54+
return errors.New("Must specify the ID of your collection with the --collection flag.")
55+
}
56+
frontClient := rest.NewFrontClient(rest.Domain, telemetry.GetClientID())
57+
_, err = util.GetOrCreateServiceIDByPostmanCollectionID(frontClient, collectionId)
58+
if err != nil {
59+
return err
60+
}
61+
62+
return setupAgentForServer(collectionId)
63+
}
64+
65+
func removeAgentFromEC2(cmd *cobra.Command, args []string) error {
66+
return fmt.Errorf("this command is not yet implemented")
67+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[Unit]
2+
Description=Postman Live Collections Agent
3+
Wants=network-online.target
4+
After=network-online.target NetworkManager.service systemd-resolved.service
5+
6+
[Service]
7+
EnvironmentFile=/etc/default/postman-lc-agent
8+
# DO NOT CHANGE
9+
# "${FOO}" uses the arguement as is, while "$FOO" splits the string on white space
10+
# Reference: https://www.freedesktop.org/software/systemd/man/systemd.service.html#Command%20lines
11+
ExecStart=/usr/bin/postman-lc-agent apidump --collection "${COLLECTION_ID}" --interfaces "${INTERFACES}" --filter "${FILTER}" "$EXTRA_APIDUMP_ARGS"
12+
13+
[Install]
14+
WantedBy=multi-user.target
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Add your Postman API key below. For example:
2+
#
3+
# POSTMAN_API_KEY=PMAC-XXXXXXX
4+
#
5+
# This is required.
6+
7+
POSTMAN_API_KEY={{.PostmanAPIKey}}
8+
9+
10+
# Add your your Postman Live Collection ID.
11+
#This is required.
12+
13+
COLLECTION_ID={{.CollectionId}}
14+
15+
# For example,
16+
# COLLECTION_ID=1234567-890abcde-f123-4567-890a-bcdef1234567
17+
18+
19+
# INTERFACES is optional. If left blank, the agent will listen on all available
20+
# network interfaces.
21+
#
22+
# FILTER is optional. If left blank, no packet-capture filter will be applied.
23+
# For example
24+
# INTERFACES=lo,eth0,eth1
25+
# FILTER="port 80 or port 8080"
26+
#
27+
28+
INTERFACES=
29+
FILTER=
30+
31+
32+
# Configure any extra arguments you wish to provide to the
33+
# 'postman-lc-agent apidump' command. For example,
34+
#
35+
# EXTRA_APIDUMP_ARGS="--rate-limit 100"
36+
#
37+
# This is optional and can be left blank.
38+
39+
EXTRA_APIDUMP_ARGS=

0 commit comments

Comments
 (0)