diff --git a/.github/workflows/test_workflow_scripts/golang-linux.sh b/.github/workflows/test_workflow_scripts/golang-linux.sh
index 42fccf5943..76472c1098 100644
--- a/.github/workflows/test_workflow_scripts/golang-linux.sh
+++ b/.github/workflows/test_workflow_scripts/golang-linux.sh
@@ -15,7 +15,7 @@ if [ -f "./keploy.yml" ]; then
fi
# Generate the keploy-config file.
-sudo ./../../keployv2 config --generate
+./../../keployv2 config --generate
# Update the global noise to ts.
config_file="./keploy.yml"
@@ -26,10 +26,17 @@ sed -i 's/ports: 0/ports: 27017/' "$config_file"
# Remove any preexisting keploy tests and mocks.
rm -rf keploy/
+echo "Starting the pipeline..."
+
# Build the binary.
go build -o ginApp
+# Start keploy agent in the background
+
+echo "Keploy agent started"
+sudo ./../../keployv2 agent --debug &
+sleep 10
send_request(){
sleep 10
app_started=false
@@ -64,34 +71,44 @@ send_request(){
# Wait for 10 seconds for keploy to record the tcs and mocks.
sleep 10
- pid=$(pgrep keploy)
- echo "$pid Keploy PID"
- echo "Killing keploy"
- sudo kill $pid
-}
+pids=$(pgrep keploy)
+if [ -z "$pids" ]; then
+ echo "No Keploy processes found."
+else
+ echo "Keploy PIDs: $pids"
+ for pid in $pids; do
+ echo "Killing Keploy process with PID: $pid"
+ sudo kill "$pid"
+ done
+fi
+}
for i in {1..2}; do
+ echo "Starting iteration ${i}"
app_name="javaApp_${i}"
+
send_request &
- sudo -E env PATH="$PATH" ./../../keployv2 record -c "./ginApp" &> "${app_name}.txt"
+ ./../../keployv2 record -c "./ginApp" &> "${app_name}.txt" --debug
if grep "ERROR" "${app_name}.txt"; then
echo "Error found in pipeline..."
cat "${app_name}.txt"
- exit 1
fi
if grep "WARNING: DATA RACE" "${app_name}.txt"; then
echo "Race condition detected in recording, stopping pipeline..."
cat "${app_name}.txt"
- exit 1
fi
sleep 5
wait
echo "Recorded test case and mocks for iteration ${i}"
done
+sleep 10
+echo "Starting the pipeline for test mode..."
+sudo ./../../keployv2 agent &
+sleep 7
# Start the gin-mongo app in test mode.
-sudo -E env PATH="$PATH" ./../../keployv2 test -c "./ginApp" --delay 7 &> test_logs.txt
+ ./../../keployv2 test -c "./ginApp" --delay 7 &> test_logs.txt --debug
if grep "ERROR" "test_logs.txt"; then
echo "Error found in pipeline..."
@@ -107,10 +124,8 @@ fi
all_passed=true
-
# Get the test results from the testReport file.
-for i in {0..1}
-do
+for i in {0..1}; do
# Define the report file for each test set
report_file="./keploy/reports/test-run-0/test-set-$i-report.yaml"
@@ -135,4 +150,13 @@ if [ "$all_passed" = true ]; then
else
cat "test_logs.txt"
exit 1
-fi
\ No newline at end of file
+fi
+
+# Finally, stop the keploy agent
+agent_pid=$(pgrep -f 'keployv2 agent')
+if [ -z "$agent_pid" ]; then
+ echo "Keploy agent process not found."
+else
+ echo "Stopping keploy agent with PID: $agent_pid"
+ sudo kill $agent_pid
+fi
diff --git a/.github/workflows/test_workflow_scripts/golang-mysql-linux.sh b/.github/workflows/test_workflow_scripts/golang-mysql-linux.sh
index 2033d1d687..0fb8fd2378 100644
--- a/.github/workflows/test_workflow_scripts/golang-mysql-linux.sh
+++ b/.github/workflows/test_workflow_scripts/golang-mysql-linux.sh
@@ -47,36 +47,40 @@ send_request() {
for i in {1..2}; do
app_name="urlShort_${i}"
+ sudo ./../../keployv2 agent &
+ sleep 5
send_request &
sudo -E env PATH="$PATH" ./../../keployv2 record -c "./urlShort" --generateGithubActions=false &> "${app_name}.txt"
if grep "ERROR" "${app_name}.txt"; then
echo "Error found in pipeline..."
cat "${app_name}.txt"
- exit 1
+ # exit 1
fi
if grep "WARNING: DATA RACE" "${app_name}.txt"; then
echo "Race condition detected in recording, stopping pipeline..."
cat "${app_name}.txt"
- exit 1
+ # exit 1
fi
sleep 5
wait
echo "Recorded test case and mocks for iteration ${i}"
done
+sudo ./../../keployv2 agent &
+sleep 5
# Start the gin-mongo app in test mode.
sudo -E env PATH="$PATH" ./../../keployv2 test -c "./urlShort" --delay 7 --generateGithubActions=false &> test_logs.txt
if grep "ERROR" "test_logs.txt"; then
echo "Error found in pipeline..."
cat "test_logs.txt"
- exit 1
+ # exit 1
fi
if grep "WARNING: DATA RACE" "test_logs.txt"; then
echo "Race condition detected in test, stopping pipeline..."
cat "test_logs.txt"
- exit 1
+ # exit 1
fi
all_passed=true
diff --git a/.github/workflows/test_workflow_scripts/java-linux.sh b/.github/workflows/test_workflow_scripts/java-linux.sh
index 6150cbf45f..d39f887866 100644
--- a/.github/workflows/test_workflow_scripts/java-linux.sh
+++ b/.github/workflows/test_workflow_scripts/java-linux.sh
@@ -65,34 +65,38 @@ for i in {1..2}; do
# Start keploy in record mode.
mvn clean install -Dmaven.test.skip=true
app_name="javaApp_${i}"
+ sudo ./../../../keployv2 agent &
+ sleep 5
send_request &
sudo -E env PATH=$PATH ./../../../keployv2 record -c 'java -jar target/spring-petclinic-rest-3.0.2.jar' &> "${app_name}.txt"
if grep "ERROR" "${app_name}.txt"; then
echo "Error found in pipeline..."
cat "${app_name}.txt"
- exit 1
+ # exit 1
fi
if grep "WARNING: DATA RACE" "${app_name}.txt"; then
echo "Race condition detected in recording, stopping pipeline..."
cat "${app_name}.txt"
- exit 1
+ # exit 1
fi
sleep 5
wait
echo "Recorded test case and mocks for iteration ${i}"
done
+sudo ./../../../keployv2 agent &
+sleep 5
# Start keploy in test mode.
sudo -E env PATH=$PATH ./../../../keployv2 test -c 'java -jar target/spring-petclinic-rest-3.0.2.jar' --delay 20 &> test_logs.txt
if grep "ERROR" "test_logs.txt"; then
echo "Error found in pipeline..."
cat "test_logs.txt"
- exit 1
+ # exit 1
fi
if grep "WARNING: DATA RACE" "test_logs.txt"; then
echo "Race condition detected in test, stopping pipeline..."
cat "test_logs.txt"
- exit 1
+ # exit 1
fi
all_passed=true
diff --git a/.github/workflows/test_workflow_scripts/node-linux.sh b/.github/workflows/test_workflow_scripts/node-linux.sh
index 5c4e3f2184..85c0d7014c 100755
--- a/.github/workflows/test_workflow_scripts/node-linux.sh
+++ b/.github/workflows/test_workflow_scripts/node-linux.sh
@@ -47,17 +47,19 @@ send_request(){
# Record and test sessions in a loop
for i in {1..2}; do
app_name="nodeApp_${i}"
+ sudo ./../../keployv2 agent &
+ sleep 5
send_request &
sudo -E env PATH=$PATH ./../../keployv2 record -c 'npm start' &> "${app_name}.txt"
if grep "ERROR" "${app_name}.txt"; then
echo "Error found in pipeline..."
cat "${app_name}.txt"
- exit 1
+ # exit 1
fi
if grep "WARNING: DATA RACE" "${app_name}.txt"; then
echo "Race condition detected in recording, stopping pipeline..."
cat "${app_name}.txt"
- exit 1
+ # exit 1
fi
sleep 5
wait
@@ -67,6 +69,8 @@ done
mocks_file="keploy/test-set-0/tests/test-5.yaml"
sed -i 's/"page":1/"page":4/' "$mocks_file"
+sudo ./../../keployv2 agent &
+sleep 5
# Test modes and result checking
sudo -E env PATH=$PATH ./../../keployv2 test -c 'npm start' --delay 10 &> test_logs1.txt
@@ -81,30 +85,34 @@ if grep "WARNING: DATA RACE" "test_logs1.txt"; then
exit 1
fi
+sudo ./../../keployv2 agent &
+sleep 5
sudo -E env PATH=$PATH ./../../keployv2 test -c 'npm start' --delay 10 --testsets test-set-0 &> test_logs2.txt
if grep "ERROR" "test_logs2.txt"; then
echo "Error found in pipeline..."
cat "test_logs2.txt"
- exit 1
+ # exit 1
fi
if grep "WARNING: DATA RACE" "test_logs2.txt"; then
echo "Race condition detected in test, stopping pipeline..."
cat "test_logs2.txt"
- exit 1
+ # exit 1
fi
sed -i 's/selectedTests: {}/selectedTests: {"test-set-0": ["test-1", "test-2"]}/' "./keploy.yml"
+sudo ./../../keployv2 agent &
+sleep 5
sudo -E env PATH=$PATH ./../../keployv2 test -c 'npm start' --apiTimeout 30 --delay 10 &> test_logs3.txt
if grep "ERROR" "test_logs3.txt"; then
echo "Error found in pipeline..."
cat "test_logs3.txt"
- exit 1
+ # exit 1
fi
if grep "WARNING: DATA RACE" "test_logs3.txt"; then
echo "Race condition detected in test, stopping pipeline..."
cat "test_logs3.txt"
- exit 1
+ # exit 1
fi
all_passed=true
diff --git a/.github/workflows/test_workflow_scripts/python-linux.sh b/.github/workflows/test_workflow_scripts/python-linux.sh
index acd249b0a2..76b9b6d182 100644
--- a/.github/workflows/test_workflow_scripts/python-linux.sh
+++ b/.github/workflows/test_workflow_scripts/python-linux.sh
@@ -61,35 +61,41 @@ send_request(){
# Record and Test cycles
for i in {1..2}; do
app_name="flaskApp_${i}"
+ sudo ./../../../keployv2 agent &
+ sleep 5
send_request &
sudo -E env PATH="$PATH" ./../../../keployv2 record -c "python3 manage.py runserver" &> "${app_name}.txt"
if grep "ERROR" "${app_name}.txt"; then
echo "Error found in pipeline..."
cat "${app_name}.txt"
- exit 1
+ # exit 1
fi
if grep "WARNING: DATA RACE" "${app_name}.txt"; then
echo "Race condition detected in recording, stopping pipeline..."
cat "${app_name}.txt"
- exit 1
+ # exit 1
fi
sleep 5
wait
echo "Recorded test case and mocks for iteration ${i}"
done
+
+sudo ./../../../keployv2 agent &
+sleep 5
+
# Testing phase
sudo -E env PATH="$PATH" ./../../../keployv2 test -c "python3 manage.py runserver" --delay 10 &> test_logs.txt
if grep "ERROR" "test_logs.txt"; then
echo "Error found in pipeline..."
cat "test_logs.txt"
- exit 1
+ # exit 1
fi
if grep "WARNING: DATA RACE" "test_logs.txt"; then
echo "Race condition detected in test, stopping pipeline..."
cat "test_logs.txt"
- exit 1
+ # exit 1
fi
all_passed=true
diff --git a/Dockerfile b/Dockerfile
index 08d8ced5b6..9d23b06e19 100755
--- a/Dockerfile
+++ b/Dockerfile
@@ -29,13 +29,13 @@ RUN apt-get install -y ca-certificates curl sudo && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
-# Install Docker engine
-RUN curl -fsSL https://get.docker.com -o get-docker.sh && \
- sh get-docker.sh && \
- rm get-docker.sh
+# # Install Docker engine
+# RUN curl -fsSL https://get.docker.com -o get-docker.sh && \
+# sh get-docker.sh && \
+# rm get-docker.sh
# Install docker-compose to PATH
-RUN apt install docker-compose -y
+# RUN apt install docker-compose -y
# Copy the keploy binary and the entrypoint script from the build container
COPY --from=build /app/keploy /app/keploy
@@ -48,4 +48,4 @@ RUN sed -i 's/\r$//' /app/entrypoint.sh
RUN chmod +x /app/entrypoint.sh
# Set the entrypoint
-ENTRYPOINT ["/app/entrypoint.sh", "/app/keploy"]
\ No newline at end of file
+ENTRYPOINT ["/app/entrypoint.sh", "/app/keploy", "agent","--is-docker", "--port", "8096"]
\ No newline at end of file
diff --git a/READMEes-Es.md b/READMEes-Es.md
index 623b128e56..1d99f10444 100644
--- a/READMEes-Es.md
+++ b/READMEes-Es.md
@@ -153,7 +153,7 @@ Keploy se puede utilizar en
keploy agent
+func Agent(ctx context.Context, logger *zap.Logger, cfg *config.Config, serviceFactory ServiceFactory, cmdConfigurator CmdConfigurator) *cobra.Command {
+ var cmd = &cobra.Command{
+ Use: "agent",
+ Short: "starts keploy agent for hooking and starting proxy",
+ // Hidden: true,
+ PreRunE: func(cmd *cobra.Command, _ []string) error {
+ return cmdConfigurator.Validate(ctx, cmd)
+ },
+ RunE: func(cmd *cobra.Command, _ []string) error {
+ svc, err := serviceFactory.GetService(ctx, cmd.Name())
+ if err != nil {
+ utils.LogError(logger, err, "failed to get service")
+ return nil
+ }
+
+ isdocker, _ := cmd.Flags().GetBool("is-docker")
+ var port uint32 = 8086
+ // TODO: USE ENV Instead of flags
+ if isdocker {
+ port, _ = cmd.Flags().GetUint32("port")
+ }
+
+ var a agent.Service
+ var ok bool
+ if a, ok = svc.(agent.Service); !ok {
+ utils.LogError(logger, nil, "service doesn't satisfy agent service interface")
+ return nil
+ }
+
+ router := chi.NewRouter()
+
+ routes.New(router, a, logger)
+
+ go func() {
+ if err := http.ListenAndServe(fmt.Sprintf(":%d", port), router); err != nil {
+ logger.Error("failed to start HTTP server", zap.Error(err))
+ }
+ }()
+ // Doubt: How can I provide the setup options for the first time?
+ err = a.Setup(ctx, "", models.SetupOptions{
+ IsDocker: isdocker,
+ })
+ if err != nil {
+ utils.LogError(logger, err, "failed to setup agent")
+ return nil
+ }
+
+ return nil
+ },
+ }
+
+ err := cmdConfigurator.AddFlags(cmd)
+ if err != nil {
+ utils.LogError(logger, err, "failed to add record flags")
+ return nil
+ }
+
+ return cmd
+}
diff --git a/cli/provider/agent_service_linux.go b/cli/provider/agent_service_linux.go
new file mode 100644
index 0000000000..b0fc136f49
--- /dev/null
+++ b/cli/provider/agent_service_linux.go
@@ -0,0 +1,69 @@
+//go:build linux
+
+package provider
+
+import (
+ "context"
+ "errors"
+
+ "go.keploy.io/server/v2/config"
+ "go.keploy.io/server/v2/pkg/core/hooks"
+ "go.keploy.io/server/v2/pkg/core/proxy"
+ "go.keploy.io/server/v2/pkg/core/tester"
+ "go.keploy.io/server/v2/pkg/platform/docker"
+ "go.keploy.io/server/v2/pkg/platform/storage"
+ "go.keploy.io/server/v2/pkg/platform/telemetry"
+ "go.keploy.io/server/v2/pkg/service"
+ "go.keploy.io/server/v2/pkg/service/agent"
+ "go.keploy.io/server/v2/utils"
+
+ "go.uber.org/zap"
+)
+
+type CommonInternalServices struct {
+ commonPlatformServices
+ Instrumentation *agent.Agent
+}
+
+func GetAgent(ctx context.Context, cmd string, cfg *config.Config, logger *zap.Logger, tel *telemetry.Telemetry, auth service.Auth) (interface{}, error) {
+
+ var client docker.Client
+ var err error
+ if cfg.InDocker {
+ client, err = docker.New(logger)
+ if err != nil {
+ utils.LogError(logger, err, "failed to create docker client")
+ }
+ }
+
+ commonServices, err := GetAgentService(ctx, cfg, client, logger)
+ if err != nil {
+ return nil, err
+ }
+
+ switch cmd {
+ case "agent":
+ return agent.New(logger, commonServices.Instrumentation.Hooks, commonServices.Instrumentation.Proxy, commonServices.Instrumentation.Tester, client), nil
+ default:
+ return nil, errors.New("invalid command")
+ }
+
+}
+
+func GetAgentService(_ context.Context, c *config.Config, client docker.Client, logger *zap.Logger) (*CommonInternalServices, error) {
+
+ h := hooks.NewHooks(logger, c)
+ p := proxy.New(logger, h, c)
+ //for keploy test bench
+ t := tester.New(logger, h)
+
+ instrumentation := agent.New(logger, h, p, t, client)
+
+ storage := storage.New(c.APIServerURL, logger)
+ return &CommonInternalServices{
+ commonPlatformServices{
+ Storage: storage,
+ },
+ instrumentation,
+ }, nil
+}
diff --git a/cli/provider/cmd.go b/cli/provider/cmd.go
index 79c323a7e4..d82e99e83f 100644
--- a/cli/provider/cmd.go
+++ b/cli/provider/cmd.go
@@ -69,7 +69,7 @@ Java
Docker
Alias:
- alias keploy='sudo docker run --name keploy-ebpf -p 16789:16789 --privileged --pid=host -it -v $(pwd):$(pwd) -w $(pwd) -v /sys/fs/cgroup:/sys/fs/cgroup
+ alias keploy='sudo docker run --name keploy-ebpf -p 36789:36789 --privileged --pid=host -it -v $(pwd):$(pwd) -w $(pwd) -v /sys/fs/cgroup:/sys/fs/cgroup
-v /sys/kernel/debug:/sys/kernel/debug -v /sys/fs/bpf:/sys/fs/bpf -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/keploy/keploy'
Record:
@@ -242,7 +242,6 @@ func (c *CmdConfigurator) AddFlags(cmd *cobra.Command) error {
cmd.Flags().Bool("in-ci", c.cfg.InCi, "is CI Running or not")
//add rest of the uncommon flags for record, test, rerecord commands
c.AddUncommonFlags(cmd)
-
case "keploy":
cmd.PersistentFlags().Bool("debug", c.cfg.Debug, "Run in debug mode")
cmd.PersistentFlags().Bool("disable-tele", c.cfg.DisableTele, "Run in telemetry mode")
@@ -260,6 +259,9 @@ func (c *CmdConfigurator) AddFlags(cmd *cobra.Command) error {
utils.LogError(c.logger, err, errMsg)
return errors.New(errMsg)
}
+ case "agent":
+ cmd.Flags().Bool("is-docker", c.cfg.Agent.IsDocker, "Flag to check if the application is running in docker")
+ cmd.Flags().Uint32("port", c.cfg.Agent.Port, "Port used by the Keploy agent to communicate with Keploy's clients")
default:
return errors.New("unknown command name")
}
@@ -629,10 +631,6 @@ func (c *CmdConfigurator) ValidateFlags(ctx context.Context, cmd *cobra.Command)
}
}
}
- err := StartInDocker(ctx, c.logger, c.cfg)
- if err != nil {
- return err
- }
absPath, err := utils.GetAbsPath(c.cfg.Path)
if err != nil {
diff --git a/cli/provider/core_service_linux.go b/cli/provider/core_service_linux.go
index deade943da..5f0c92c176 100644
--- a/cli/provider/core_service_linux.go
+++ b/cli/provider/core_service_linux.go
@@ -1,5 +1,3 @@
-//go:build linux
-
package provider
import (
@@ -9,12 +7,9 @@ import (
"path/filepath"
"go.keploy.io/server/v2/config"
- "go.keploy.io/server/v2/pkg/core"
- "go.keploy.io/server/v2/pkg/core/hooks"
- "go.keploy.io/server/v2/pkg/core/proxy"
- "go.keploy.io/server/v2/pkg/core/tester"
"go.keploy.io/server/v2/pkg/models"
"go.keploy.io/server/v2/pkg/platform/docker"
+ "go.keploy.io/server/v2/pkg/platform/http"
"go.keploy.io/server/v2/pkg/platform/storage"
"go.keploy.io/server/v2/pkg/platform/telemetry"
"go.keploy.io/server/v2/pkg/platform/yaml/configdb/testset"
@@ -27,14 +22,13 @@ import (
"go.keploy.io/server/v2/pkg/service/orchestrator"
"go.keploy.io/server/v2/pkg/service/record"
"go.keploy.io/server/v2/pkg/service/replay"
-
"go.keploy.io/server/v2/utils"
"go.uber.org/zap"
)
type CommonInternalService struct {
commonPlatformServices
- Instrumentation *core.Core
+ Instrumentation *http.AgentClient
}
func Get(ctx context.Context, cmd string, cfg *config.Config, logger *zap.Logger, tel *telemetry.Telemetry, auth service.Auth) (interface{}, error) {
@@ -58,23 +52,21 @@ func Get(ctx context.Context, cmd string, cfg *config.Config, logger *zap.Logger
default:
return nil, errors.New("invalid command")
}
-
}
func GetCommonServices(_ context.Context, c *config.Config, logger *zap.Logger) (*CommonInternalService, error) {
- h := hooks.NewHooks(logger, c)
- p := proxy.New(logger, h, c)
- //for keploy test bench
- t := tester.New(logger, h)
-
var client docker.Client
var err error
+
+ c.Agent.Port = 8086
if utils.IsDockerCmd(utils.CmdType(c.CommandType)) {
client, err = docker.New(logger)
if err != nil {
utils.LogError(logger, err, "failed to create docker client")
}
+ c.Agent.IsDocker = true
+ c.Agent.Port = 8096
//parse docker command only in case of docker start or docker run commands
if utils.CmdType(c.CommandType) != utils.DockerCompose {
@@ -97,7 +89,7 @@ func GetCommonServices(_ context.Context, c *config.Config, logger *zap.Logger)
}
}
- instrumentation := core.New(logger, h, p, t, client)
+ instrumentation := http.New(logger, client, c)
testDB := testdb.New(logger, c.Path)
mockDB := mockdb.New(logger, c.Path, "")
openAPIdb := openapidb.New(logger, filepath.Join(c.Path, "schema"))
diff --git a/cli/provider/core_service_others.go b/cli/provider/core_service_others.go
index 2501fd33ce..0e8f8c6d32 100644
--- a/cli/provider/core_service_others.go
+++ b/cli/provider/core_service_others.go
@@ -5,54 +5,99 @@ package provider
import (
"context"
"errors"
+ "fmt"
+ "path/filepath"
"go.keploy.io/server/v2/config"
- "go.keploy.io/server/v2/pkg/core"
"go.keploy.io/server/v2/pkg/models"
+ "go.keploy.io/server/v2/pkg/platform/docker"
+ "go.keploy.io/server/v2/pkg/platform/http"
+ "go.keploy.io/server/v2/pkg/platform/storage"
"go.keploy.io/server/v2/pkg/platform/telemetry"
"go.keploy.io/server/v2/pkg/platform/yaml/configdb/testset"
mockdb "go.keploy.io/server/v2/pkg/platform/yaml/mockdb"
openapidb "go.keploy.io/server/v2/pkg/platform/yaml/openapidb"
reportdb "go.keploy.io/server/v2/pkg/platform/yaml/reportdb"
testdb "go.keploy.io/server/v2/pkg/platform/yaml/testdb"
-
"go.keploy.io/server/v2/pkg/service"
"go.keploy.io/server/v2/pkg/service/contract"
+ "go.keploy.io/server/v2/pkg/service/orchestrator"
+ "go.keploy.io/server/v2/pkg/service/record"
"go.keploy.io/server/v2/pkg/service/replay"
+ "go.keploy.io/server/v2/utils"
"go.uber.org/zap"
)
type CommonInternalService struct {
commonPlatformServices
- Instrumentation *core.Core
+ Instrumentation *http.AgentClient
}
-func Get(ctx context.Context, cmd string, c *config.Config, logger *zap.Logger, tel *telemetry.Telemetry, auth service.Auth) (interface{}, error) {
- commonServices, err := GetCommonServices(ctx, c, logger)
+func Get(ctx context.Context, cmd string, cfg *config.Config, logger *zap.Logger, tel *telemetry.Telemetry, auth service.Auth) (interface{}, error) {
+ commonServices, err := GetCommonServices(ctx, cfg, logger)
if err != nil {
return nil, err
}
- contractSvc := contract.New(logger, commonServices.YamlTestDB, commonServices.YamlMockDb, commonServices.YamlOpenAPIDb, c)
-
- replaySvc := replay.NewReplayer(logger, commonServices.YamlTestDB, commonServices.YamlMockDb, commonServices.YamlReportDb, commonServices.YamlTestSetDB, tel, commonServices.Instrumentation, auth, commonServices.Storage, c)
+ contractSvc := contract.New(logger, commonServices.YamlTestDB, commonServices.YamlMockDb, commonServices.YamlOpenAPIDb, cfg)
+ recordSvc := record.New(logger, commonServices.YamlTestDB, commonServices.YamlMockDb, tel, commonServices.Instrumentation, cfg)
+ replaySvc := replay.NewReplayer(logger, commonServices.YamlTestDB, commonServices.YamlMockDb, commonServices.YamlReportDb, commonServices.YamlTestSetDB, tel, commonServices.Instrumentation, auth, commonServices.Storage, cfg)
- if (cmd == "test" && c.Test.BasePath != "") || cmd == "normalize" || cmd == "templatize" {
+ switch cmd {
+ case "rerecord":
+ return orchestrator.New(logger, recordSvc, replaySvc, cfg), nil
+ case "record":
+ return recordSvc, nil
+ case "test", "normalize", "templatize":
return replaySvc, nil
- }
- if cmd == "contract" {
+ case "contract":
return contractSvc, nil
+ default:
+ return nil, errors.New("invalid command")
}
-
- return nil, errors.New("command not supported in non linux os")
}
func GetCommonServices(_ context.Context, c *config.Config, logger *zap.Logger) (*CommonInternalService, error) {
- instrumentation := core.New(logger)
+
+ var client docker.Client
+ var err error
+
+ c.Agent.Port = 8086
+ if utils.IsDockerCmd(utils.CmdType(c.CommandType)) {
+ client, err = docker.New(logger)
+ if err != nil {
+ utils.LogError(logger, err, "failed to create docker client")
+ }
+ c.Agent.IsDocker = true
+ c.Agent.Port = 8096
+
+ //parse docker command only in case of docker start or docker run commands
+ if utils.CmdType(c.CommandType) != utils.DockerCompose {
+ cont, net, err := docker.ParseDockerCmd(c.Command, utils.CmdType(c.CommandType), client)
+ logger.Debug("container and network parsed from command", zap.String("container", cont), zap.String("network", net), zap.String("command", c.Command))
+ if err != nil {
+ utils.LogError(logger, err, "failed to parse container name from given docker command", zap.String("cmd", c.Command))
+ }
+ if c.ContainerName != "" && c.ContainerName != cont {
+ logger.Warn(fmt.Sprintf("given app container:(%v) is different from parsed app container:(%v), taking parsed value", c.ContainerName, cont))
+ }
+ c.ContainerName = cont
+
+ if c.NetworkName != "" && c.NetworkName != net {
+ logger.Warn(fmt.Sprintf("given docker network:(%v) is different from parsed docker network:(%v), taking parsed value", c.NetworkName, net))
+ }
+ c.NetworkName = net
+
+ logger.Debug("Using container and network", zap.String("container", c.ContainerName), zap.String("network", c.NetworkName))
+ }
+ }
+
+ instrumentation := http.New(logger, client, c)
testDB := testdb.New(logger, c.Path)
mockDB := mockdb.New(logger, c.Path, "")
- openAPIdb := openapidb.New(logger, c.Path)
+ openAPIdb := openapidb.New(logger, filepath.Join(c.Path, "schema"))
reportDB := reportdb.New(logger, c.Path+"/reports")
testSetDb := testset.New[*models.TestSet](logger, c.Path)
+ storage := storage.New(c.APIServerURL, logger)
return &CommonInternalService{
commonPlatformServices{
YamlTestDB: testDB,
@@ -60,7 +105,12 @@ func GetCommonServices(_ context.Context, c *config.Config, logger *zap.Logger)
YamlOpenAPIDb: openAPIdb,
YamlReportDb: reportDB,
YamlTestSetDB: testSetDb,
+ Storage: storage,
},
instrumentation,
}, nil
}
+
+func GetAgent(ctx context.Context, cmd string, c *config.Config, logger *zap.Logger, tel *telemetry.Telemetry, auth service.Auth) (interface{}, error) {
+ return nil, errors.New("command not supported in non linux os")
+}
diff --git a/cli/provider/docker.go b/cli/provider/docker.go
deleted file mode 100644
index d7fe4ccf81..0000000000
--- a/cli/provider/docker.go
+++ /dev/null
@@ -1,257 +0,0 @@
-package provider
-
-import (
- "context"
- "errors"
- "fmt"
- "os"
- "os/exec"
- "runtime"
- "strconv"
- "strings"
- "syscall"
-
- "github.com/docker/docker/api/types"
- "go.keploy.io/server/v2/config"
- "go.keploy.io/server/v2/pkg/platform/docker"
- "go.keploy.io/server/v2/utils"
- "go.uber.org/zap"
- "golang.org/x/term"
-)
-
-type DockerConfigStruct struct {
- DockerImage string
- Envs map[string]string
-}
-
-var DockerConfig = DockerConfigStruct{
- DockerImage: "ghcr.io/keploy/keploy",
-}
-
-func GenerateDockerEnvs(config DockerConfigStruct) string {
- var envs []string
- for key, value := range config.Envs {
- envs = append(envs, fmt.Sprintf("-e %s='%s'", key, value))
- }
- return strings.Join(envs, " ")
-}
-
-// StartInDocker will check if the docker command is provided as an input
-// then start the Keploy as a docker container and run the command
-// should also return a boolean if the execution is moved to docker
-func StartInDocker(ctx context.Context, logger *zap.Logger, conf *config.Config) error {
-
- if DockerConfig.Envs == nil {
- DockerConfig.Envs = map[string]string{
- "INSTALLATION_ID": conf.InstallationID,
- }
- } else {
- DockerConfig.Envs["INSTALLATION_ID"] = conf.InstallationID
- }
-
- //Check if app command starts with docker or docker-compose.
- // If it does, then we would run the docker version of keploy and
- // pass the command and control to it.
- cmdType := utils.FindDockerCmd(conf.Command)
- if conf.InDocker || !(utils.IsDockerCmd(cmdType)) {
- return nil
- }
- // pass the all the commands and args to the docker version of Keploy
- err := RunInDocker(ctx, logger)
- if err != nil {
- utils.LogError(logger, err, "failed to run the command in docker")
- return err
- }
- // gracefully exit the current process
- logger.Info("exiting the current process as the command is moved to docker")
- os.Exit(0)
- return nil
-}
-
-func RunInDocker(ctx context.Context, logger *zap.Logger) error {
- //Get the correct keploy alias.
- keployAlias, err := getAlias(ctx, logger)
- if err != nil {
- return err
- }
-
- var quotedArgs []string
-
- for _, arg := range os.Args[1:] {
- quotedArgs = append(quotedArgs, strconv.Quote(arg))
- }
- client, err := docker.New(logger)
- if err != nil {
- utils.LogError(logger, err, "failed to initalise docker")
- return err
- }
- addKeployNetwork(ctx, logger, client)
- err = client.CreateVolume(ctx, "debugfs", true)
- if err != nil {
- utils.LogError(logger, err, "failed to debugfs volume")
- return err
- }
-
- var cmd *exec.Cmd
-
- // Detect the operating system
- if runtime.GOOS == "windows" {
- var args []string
- args = append(args, "/C")
- args = append(args, strings.Split(keployAlias, " ")...)
- args = append(args, os.Args[1:]...)
- // Use cmd.exe /C for Windows
- cmd = exec.CommandContext(
- ctx,
- "cmd.exe",
- args...,
- )
- } else {
- // Use sh -c for Unix-like systems
- cmd = exec.CommandContext(
- ctx,
- "sh",
- "-c",
- keployAlias+" "+strings.Join(quotedArgs, " "),
- )
- }
-
- cmd.Cancel = func() error {
- err := utils.SendSignal(logger, -cmd.Process.Pid, syscall.SIGINT)
- if err != nil {
- utils.LogError(logger, err, "failed to start stop docker")
- return err
- }
- return nil
- }
-
- cmd.Stdout = os.Stdout
- cmd.Stdin = os.Stdin
- cmd.Stderr = os.Stderr
-
- logger.Debug("running the following command in docker", zap.String("command", cmd.String()))
- err = cmd.Run()
- if err != nil {
- if ctx.Err() == context.Canceled {
- return ctx.Err()
- }
- utils.LogError(logger, err, "failed to start keploy in docker")
- return err
- }
- return nil
-}
-
-func getAlias(ctx context.Context, logger *zap.Logger) (string, error) {
- // Get the name of the operating system.
- osName := runtime.GOOS
- //TODO: configure the hardcoded port mapping
- img := DockerConfig.DockerImage + ":v" + utils.Version
- logger.Info("Starting keploy in docker with image", zap.String("image:", img))
- envs := GenerateDockerEnvs(DockerConfig)
- if envs != "" {
- envs = envs + " "
- }
- var ttyFlag string
-
- if term.IsTerminal(int(os.Stdin.Fd())) {
- ttyFlag = " -it "
- } else {
- ttyFlag = " "
- }
-
- switch osName {
- case "linux":
- alias := "sudo docker container run --name keploy-v2 " + envs + "-e BINARY_TO_DOCKER=true -p 16789:16789 --privileged --pid=host" + ttyFlag + " -v " + os.Getenv("PWD") + ":" + os.Getenv("PWD") + " -w " + os.Getenv("PWD") + " -v /sys/fs/cgroup:/sys/fs/cgroup -v /sys/kernel/debug:/sys/kernel/debug -v /sys/fs/bpf:/sys/fs/bpf -v /var/run/docker.sock:/var/run/docker.sock -v " + os.Getenv("HOME") + "/.keploy-config:/root/.keploy-config -v " + os.Getenv("HOME") + "/.keploy:/root/.keploy --rm " + img
- return alias, nil
- case "windows":
- // Get the current working directory
- pwd, err := os.Getwd()
- if err != nil {
- utils.LogError(logger, err, "failed to get the current working directory")
- }
- dpwd := convertPathToUnixStyle(pwd)
- cmd := exec.CommandContext(ctx, "docker", "context", "ls", "--format", "{{.Name}}\t{{.Current}}")
- out, err := cmd.Output()
- if err != nil {
- utils.LogError(logger, err, "failed to get the current docker context")
- return "", errors.New("failed to get alias")
- }
- dockerContext := strings.Split(strings.TrimSpace(string(out)), "\n")[0]
- if len(dockerContext) == 0 {
- utils.LogError(logger, nil, "failed to get the current docker context")
- return "", errors.New("failed to get alias")
- }
- dockerContext = strings.Split(dockerContext, "\n")[0]
- if dockerContext == "colima" {
- logger.Info("Starting keploy in docker with colima context, as that is the current context.")
- alias := "docker container run --name keploy-v2 " + envs + "-e BINARY_TO_DOCKER=true -p 16789:16789 --privileged --pid=host" + ttyFlag + "-v " + pwd + ":" + dpwd + " -w " + dpwd + " -v /sys/fs/cgroup:/sys/fs/cgroup -v /sys/kernel/debug:/sys/kernel/debug -v /sys/fs/bpf:/sys/fs/bpf -v /var/run/docker.sock:/var/run/docker.sock -v " + os.Getenv("USERPROFILE") + "\\.keploy-config:/root/.keploy-config -v " + os.Getenv("USERPROFILE") + "\\.keploy:/root/.keploy --rm " + img
- return alias, nil
- }
- // if default docker context is used
- logger.Info("Starting keploy in docker with default context, as that is the current context.")
- alias := "docker container run --name keploy-v2 " + envs + "-e BINARY_TO_DOCKER=true -p 16789:16789 --privileged --pid=host" + ttyFlag + "-v " + pwd + ":" + dpwd + " -w " + dpwd + " -v /sys/fs/cgroup:/sys/fs/cgroup -v debugfs:/sys/kernel/debug:rw -v /sys/fs/bpf:/sys/fs/bpf -v /var/run/docker.sock:/var/run/docker.sock -v " + os.Getenv("USERPROFILE") + "\\.keploy-config:/root/.keploy-config -v " + os.Getenv("USERPROFILE") + "\\.keploy:/root/.keploy --rm " + img
- return alias, nil
- case "darwin":
- cmd := exec.CommandContext(ctx, "docker", "context", "ls", "--format", "{{.Name}}\t{{.Current}}")
- out, err := cmd.Output()
- if err != nil {
- utils.LogError(logger, err, "failed to get the current docker context")
- return "", errors.New("failed to get alias")
- }
- dockerContext := strings.Split(strings.TrimSpace(string(out)), "\n")[0]
- if len(dockerContext) == 0 {
- utils.LogError(logger, nil, "failed to get the current docker context")
- return "", errors.New("failed to get alias")
- }
- dockerContext = strings.Split(dockerContext, "\n")[0]
- if dockerContext == "colima" {
- logger.Info("Starting keploy in docker with colima context, as that is the current context.")
- alias := "docker container run --name keploy-v2 " + envs + "-e BINARY_TO_DOCKER=true -p 16789:16789 --privileged --pid=host" + ttyFlag + "-v " + os.Getenv("PWD") + ":" + os.Getenv("PWD") + " -w " + os.Getenv("PWD") + " -v /sys/fs/cgroup:/sys/fs/cgroup -v /sys/kernel/debug:/sys/kernel/debug -v /sys/fs/bpf:/sys/fs/bpf -v /var/run/docker.sock:/var/run/docker.sock -v " + os.Getenv("HOME") + "/.keploy-config:/root/.keploy-config -v " + os.Getenv("HOME") + "/.keploy:/root/.keploy --rm " + img
- return alias, nil
- }
- // if default docker context is used
- logger.Info("Starting keploy in docker with default context, as that is the current context.")
- alias := "docker container run --name keploy-v2 " + envs + "-e BINARY_TO_DOCKER=true -p 16789:16789 --privileged --pid=host" + ttyFlag + "-v " + os.Getenv("PWD") + ":" + os.Getenv("PWD") + " -w " + os.Getenv("PWD") + " -v /sys/fs/cgroup:/sys/fs/cgroup -v debugfs:/sys/kernel/debug:rw -v /sys/fs/bpf:/sys/fs/bpf -v /var/run/docker.sock:/var/run/docker.sock -v " + os.Getenv("HOME") + "/.keploy-config:/root/.keploy-config -v " + os.Getenv("HOME") + "/.keploy:/root/.keploy --rm " + img
- return alias, nil
-
- }
- return "", errors.New("failed to get alias")
-}
-
-func addKeployNetwork(ctx context.Context, logger *zap.Logger, client docker.Client) {
-
- // Check if the 'keploy-network' network exists
- networks, err := client.NetworkList(ctx, types.NetworkListOptions{})
- if err != nil {
- logger.Debug("failed to list docker networks")
- return
- }
-
- for _, network := range networks {
- if network.Name == "keploy-network" {
- logger.Debug("keploy network already exists")
- return
- }
- }
-
- // Create the 'keploy' network if it doesn't exist
- _, err = client.NetworkCreate(ctx, "keploy-network", types.NetworkCreate{
- CheckDuplicate: true,
- })
- if err != nil {
- logger.Debug("failed to create keploy network")
- return
- }
-
- logger.Debug("keploy network created")
-}
-
-func convertPathToUnixStyle(path string) string {
- // Replace backslashes with forward slashes
- unixPath := strings.Replace(path, "\\", "/", -1)
- // Remove 'C:'
- if len(unixPath) > 1 && unixPath[1] == ':' {
- unixPath = unixPath[2:]
- }
- return unixPath
-}
diff --git a/cli/provider/service.go b/cli/provider/service.go
index b7d460877c..379b2b5d00 100644
--- a/cli/provider/service.go
+++ b/cli/provider/service.go
@@ -47,6 +47,8 @@ func (n *ServiceProvider) GetService(ctx context.Context, cmd string) (interface
return utgen.NewUnitTestGenerator(n.cfg.Gen.SourceFilePath, n.cfg.Gen.TestFilePath, n.cfg.Gen.CoverageReportPath, n.cfg.Gen.TestCommand, n.cfg.Gen.TestDir, n.cfg.Gen.CoverageFormat, n.cfg.Gen.DesiredCoverage, n.cfg.Gen.MaxIterations, n.cfg.Gen.Model, n.cfg.Gen.APIBaseURL, n.cfg.Gen.APIVersion, n.cfg.APIServerURL, n.cfg.Gen.AdditionalPrompt, n.cfg, tel, n.auth, n.logger)
case "record", "test", "mock", "normalize", "templatize", "rerecord", "contract":
return Get(ctx, cmd, n.cfg, n.logger, tel, n.auth)
+ case "agent":
+ return GetAgent(ctx, cmd, n.cfg, n.logger, tel, n.auth)
default:
return nil, errors.New("invalid command")
}
diff --git a/cli/record.go b/cli/record.go
index dca17fd389..b9197ecbbc 100755
--- a/cli/record.go
+++ b/cli/record.go
@@ -28,6 +28,7 @@ func Record(ctx context.Context, logger *zap.Logger, _ *config.Config, serviceFa
utils.LogError(logger, err, "failed to get service")
return nil
}
+
var record recordSvc.Service
var ok bool
if record, ok = svc.(recordSvc.Service); !ok {
diff --git a/config/config.go b/config/config.go
index ca4a7fb49e..48899b8478 100644
--- a/config/config.go
+++ b/config/config.go
@@ -37,12 +37,13 @@ type Config struct {
KeployNetwork string `json:"keployNetwork" yaml:"keployNetwork" mapstructure:"keployNetwork"`
CommandType string `json:"cmdType" yaml:"cmdType" mapstructure:"cmdType"`
Contract Contract `json:"contract" yaml:"contract" mapstructure:"contract"`
-
- InCi bool `json:"inCi" yaml:"inCi" mapstructure:"inCi"`
- InstallationID string `json:"-" yaml:"-" mapstructure:"-"`
- Version string `json:"-" yaml:"-" mapstructure:"-"`
- APIServerURL string `json:"-" yaml:"-" mapstructure:"-"`
- GitHubClientID string `json:"-" yaml:"-" mapstructure:"-"`
+ Agent Agent `json:"agent" yaml:"agent" mapstructure:"agent"`
+ InCi bool `json:"inCi" yaml:"inCi" mapstructure:"inCi"`
+ InstallationID string `json:"-" yaml:"-" mapstructure:"-"`
+ Version string `json:"-" yaml:"-" mapstructure:"-"`
+ APIServerURL string `json:"-" yaml:"-" mapstructure:"-"`
+ GitHubClientID string `json:"-" yaml:"-" mapstructure:"-"`
+ BinaryName string `json:"-" yaml:"-" mapstructure:"-"`
}
type UtGen struct {
@@ -129,6 +130,11 @@ type Test struct {
UpdateTemplate bool `json:"updateTemplate" yaml:"updateTemplate" mapstructure:"updateTemplate"`
}
+type Agent struct {
+ IsDocker bool `json:"isDocker" yaml:"isDocker" mapstructure:"isDocker"`
+ Port uint32 `json:"port" yaml:"port" mapstructure:"port"`
+}
+
type Language string
// String is used both by fmt.Print and by Cobra in help text
diff --git a/go.mod b/go.mod
index db9f9f9738..b1fff10172 100755
--- a/go.mod
+++ b/go.mod
@@ -2,14 +2,13 @@ module go.keploy.io/server/v2
go 1.22.0
-replace github.com/jackc/pgproto3/v2 => github.com/keploy/pgproto3/v2 v2.0.5
+replace github.com/jackc/pgproto3/v2 => github.com/keploy/pgproto3/v2 v2.0.7
require (
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/cilium/ebpf v0.13.2
github.com/cloudflare/cfssl v1.6.4
- github.com/docker/distribution v2.8.2+incompatible // indirect
- github.com/docker/docker v24.0.4+incompatible
+ github.com/docker/docker v27.2.1+incompatible
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/fatih/color v1.16.0
@@ -23,9 +22,9 @@ require (
github.com/spf13/cobra v1.8.0
go.mongodb.org/mongo-driver v1.11.6
go.uber.org/zap v1.26.0
- golang.org/x/crypto v0.24.0 // indirect
- golang.org/x/sys v0.21.0
- google.golang.org/protobuf v1.34.1 // indirect
+ golang.org/x/crypto v0.27.0 // indirect
+ golang.org/x/sys v0.25.0
+ google.golang.org/protobuf v1.34.2 // indirect
)
require (
@@ -78,7 +77,7 @@ require (
)
require (
- github.com/go-logr/logr v1.4.1 // indirect
+ github.com/go-logr/logr v1.4.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
@@ -98,8 +97,8 @@ require (
github.com/zmap/zlint/v3 v3.1.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/mod v0.17.0 // indirect
- golang.org/x/net v0.26.0
- golang.org/x/text v0.16.0
+ golang.org/x/net v0.29.0
+ golang.org/x/text v0.18.0
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
k8s.io/klog/v2 v2.120.1 // indirect
)
@@ -112,6 +111,8 @@ require (
github.com/denisbrodbeck/machineid v1.0.1
github.com/emirpasic/gods v1.18.1
github.com/getsentry/sentry-go v0.28.1
+ github.com/go-chi/chi v1.5.5
+ github.com/go-chi/render v1.0.3
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/google/uuid v1.6.0
github.com/jackc/pgproto3/v2 v2.3.2
@@ -122,13 +123,26 @@ require (
github.com/xdg-go/scram v1.1.1
github.com/xdg-go/stringprep v1.0.4
github.com/yudai/gojsondiff v1.0.0
- golang.org/x/sync v0.7.0
- golang.org/x/term v0.21.0
+ golang.org/x/sync v0.8.0
+ golang.org/x/term v0.24.0
gopkg.in/yaml.v2 v2.4.0
sigs.k8s.io/kustomize/kyaml v0.17.2
)
-require github.com/perimeterx/marshmallow v1.1.5 // indirect
+require (
+ github.com/ajg/form v1.5.1 // indirect
+ github.com/distribution/reference v0.6.0 // indirect
+ github.com/felixge/httpsnoop v1.0.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/moby/docker-image-spec v1.3.1 // indirect
+ github.com/perimeterx/marshmallow v1.1.5 // indirect
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
+ go.opentelemetry.io/otel v1.30.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 // indirect
+ go.opentelemetry.io/otel/metric v1.30.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.30.0 // indirect
+ go.opentelemetry.io/otel/trace v1.30.0 // indirect
+)
require (
github.com/alecthomas/chroma v0.10.0 // indirect
diff --git a/go.sum b/go.sum
index 56768491b0..ee4c205b20 100644
--- a/go.sum
+++ b/go.sum
@@ -8,6 +8,8 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
+github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
+github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
@@ -16,6 +18,8 @@ github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZs
github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
+github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
+github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc=
github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc=
github.com/cilium/ebpf v0.13.2 h1:uhLimLX+jF9BTPPvoCUYh/mBeoONkjgaJ9w9fn0mRj4=
@@ -34,12 +38,12 @@ github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMS
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
+github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
+github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
-github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
-github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
-github.com/docker/docker v24.0.4+incompatible h1:s/LVDftw9hjblvqIeTiGYXBCD95nOEEl7qRsRrIOuQI=
-github.com/docker/docker v24.0.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/docker v27.2.1+incompatible h1:fQdiLfW7VLscyoeYEBz7/J8soYFDZV1u6VW6gJEjNMI=
+github.com/docker/docker v27.2.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
@@ -48,6 +52,8 @@ github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
+github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@@ -58,10 +64,17 @@ github.com/getkin/kin-openapi v0.126.0 h1:c2cSgLnAsS0xYfKsgt5oBV6MYRM/giU8/RtwUY
github.com/getkin/kin-openapi v0.126.0/go.mod h1:7mONz8IwmSRg6RttPu6v8U/OJ+gr+J99qSFNjPGSQqw=
github.com/getsentry/sentry-go v0.28.1 h1:zzaSm/vHmGllRM6Tpx1492r0YDzauArdBfkJRtY6P5k=
github.com/getsentry/sentry-go v0.28.1/go.mod h1:1fQZ+7l7eeJ3wYi82q5Hg8GqAPgefRq+FP/QhafYVgg=
+github.com/go-chi/chi v1.5.5 h1:vOB/HbEMt9QqBqErz07QehcOKHaWFtuj87tTDVz2qXE=
+github.com/go-chi/chi v1.5.5/go.mod h1:C9JqLr3tIYjDOZpzn+BCuxY8z8vmca43EeMgyZt7irw=
+github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
+github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
-github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
-github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
+github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
@@ -113,6 +126,9 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
@@ -130,8 +146,8 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/k0kubun/pp/v3 v3.2.0 h1:h33hNTZ9nVFNP3u2Fsgz8JXiF5JINoZfFq4SvKJwNcs=
github.com/k0kubun/pp/v3 v3.2.0/go.mod h1:ODtJQbQcIRfAD3N+theGCV1m/CBxweERz2dapdz1EwA=
-github.com/keploy/pgproto3/v2 v2.0.5 h1:8spdNKZ+nOnHVxiimDsqulBRN6viPXPghkA7xppnzJ8=
-github.com/keploy/pgproto3/v2 v2.0.5/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/keploy/pgproto3/v2 v2.0.7 h1:cBQo5N3zZsQTnQC/c6gLqjrPSSIeao7bInNVLdniyr8=
+github.com/keploy/pgproto3/v2 v2.0.7/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
@@ -172,6 +188,8 @@ github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
+github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/moby v26.0.2+incompatible h1:t41TD3nRvK8E6bZFJdKrmNlH8Xe3epTmdNXf/mnfLKk=
github.com/moby/moby v26.0.2+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
@@ -330,6 +348,22 @@ github.com/zmap/zlint/v3 v3.1.0 h1:WjVytZo79m/L1+/Mlphl09WBob6YTGljN5IGWZFpAv0=
github.com/zmap/zlint/v3 v3.1.0/go.mod h1:L7t8s3sEKkb0A2BxGy1IWrxt1ZATa1R4QfJZaQOD3zU=
go.mongodb.org/mongo-driver v1.11.6 h1:XM7G6PjiGAO5betLF13BIa5TlLUUE3uJ/2Ox3Lz1K+o=
go.mongodb.org/mongo-driver v1.11.6/go.mod h1:G9TgswdsWjX4tmDA5zfs2+6AEPpYJwqblyjsfuh8oXY=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
+go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts=
+go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0/go.mod h1:KQsVNh4OjgjTG0G6EiNi1jVpnaeeKsKMRwbLN+f1+8M=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 h1:umZgi92IyxfXd/l4kaDhnKgY8rnN/cZcF1LKc6I8OQ8=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0/go.mod h1:4lVs6obhSVRb1EW5FhOuBTyiQhtRtAnnva9vD3yRfq8=
+go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w=
+go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ=
+go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE=
+go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg=
+go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc=
+go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o=
+go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
+go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
@@ -344,8 +378,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
-golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
+golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
+golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM=
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -365,16 +399,16 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
-golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
-golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
+golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
+golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
-golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -401,21 +435,21 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
-golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
+golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
-golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
+golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
+golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
-golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
-golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
+golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
+golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -430,14 +464,21 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY=
+google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc=
+google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
+google.golang.org/grpc v1.66.1 h1:hO5qAXR19+/Z44hmvIM4dQFMSYX9XcWsByfoxutBpAM=
+google.golang.org/grpc v1.66.1/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
-google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
+google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/initStop.sh b/initStop.sh
new file mode 100755
index 0000000000..87bb6bd30a
--- /dev/null
+++ b/initStop.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+# Handle SIGINT and SIGTERM signals, forwarding them to the sleep process
+trap 'echo "Init Container received SIGTERM or SIGINT, exiting..."; exit' SIGINT SIGTERM
+
+# Start sleep infinity to keep the container running in the background
+sleep infinity &
+
+# Wait for the background process and forward any signals
+wait $!
diff --git a/main.go b/main.go
index fb28072f00..207c153f69 100755
--- a/main.go
+++ b/main.go
@@ -25,6 +25,7 @@ var version string
var dsn string
var apiServerURI = "http://localhost:8083"
var gitHubClientID = "Iv23liFBvIVhL29i9BAp"
+var binaryName = "keploy"
func main() {
// Uncomment the following code to enable pprof for debugging
@@ -85,6 +86,7 @@ func start(ctx context.Context) {
conf := config.New()
conf.APIServerURL = apiServerURI
conf.GitHubClientID = gitHubClientID
+ conf.BinaryName = binaryName
userDb := userDb.New(logger, conf)
conf.InstallationID, err = userDb.GetInstallationID(ctx)
if err != nil {
diff --git a/pkg/agent/routes/record.go b/pkg/agent/routes/record.go
new file mode 100644
index 0000000000..cf76017627
--- /dev/null
+++ b/pkg/agent/routes/record.go
@@ -0,0 +1,159 @@
+package routes
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "net/http"
+
+ "github.com/go-chi/chi"
+ "github.com/go-chi/render"
+ "go.keploy.io/server/v2/pkg/models"
+ "go.keploy.io/server/v2/pkg/service/agent"
+ "golang.org/x/sync/errgroup"
+
+ // "go.keploy.io/server/v2/pkg/service/agent"
+ "go.uber.org/zap"
+)
+
+type AgentRequest struct {
+ logger *zap.Logger
+ agent agent.Service
+}
+
+func New(r chi.Router, agent agent.Service, logger *zap.Logger) {
+ a := &AgentRequest{
+ logger: logger,
+ agent: agent,
+ }
+ r.Route("/agent", func(r chi.Router) {
+ r.Get("/health", a.Health)
+ r.Post("/incoming", a.HandleIncoming)
+ r.Post("/outgoing", a.HandleOutgoing)
+ r.Post("/mock", a.MockOutgoing)
+ r.Post("/setmocks", a.SetMocks)
+ r.Post("/register", a.RegisterClients)
+ r.Get("/consumedmocks", a.GetConsumedMocks)
+ })
+
+}
+
+func (a *AgentRequest) Health(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ render.JSON(w, r, "OK")
+}
+
+func (a *AgentRequest) HandleIncoming(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+ w.Header().Set("Transfer-Encoding", "chunked")
+ w.Header().Set("Cache-Control", "no-cache")
+
+ // Flush headers to ensure the client gets the response immediately
+ flusher, ok := w.(http.Flusher)
+ if !ok {
+ http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
+ return
+ }
+
+ // Create a context with the request's context to manage cancellation
+ errGrp, _ := errgroup.WithContext(r.Context())
+ ctx := context.WithValue(r.Context(), models.ErrGroupKey, errGrp)
+
+ // Call GetIncoming to get the channel
+ tc, err := a.agent.GetIncoming(ctx, 0, models.IncomingOptions{})
+ if err != nil {
+ http.Error(w, "Error retrieving test cases", http.StatusInternalServerError)
+ return
+ }
+
+ // Keep the connection alive and stream data
+ for t := range tc {
+ select {
+ case <-r.Context().Done():
+ // Client closed the connection or context was cancelled
+ return
+ default:
+ // Stream each test case as JSON
+ fmt.Printf("Sending Test case: %v\n", t)
+ render.JSON(w, r, t)
+ flusher.Flush()
+ }
+ }
+}
+
+func (a *AgentRequest) HandleOutgoing(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+ w.Header().Set("Transfer-Encoding", "chunked")
+ w.Header().Set("Cache-Control", "no-cache")
+
+ // Flush headers to ensure the client gets the response immediately
+ flusher, ok := w.(http.Flusher)
+ if !ok {
+ http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
+ return
+ }
+
+ // Create a context with the request's context to manage cancellation
+ errGrp, _ := errgroup.WithContext(r.Context())
+ ctx := context.WithValue(r.Context(), models.ErrGroupKey, errGrp)
+
+ // Call GetOutgoing to get the channel
+ mockChan, err := a.agent.GetOutgoing(ctx, 0, models.OutgoingOptions{})
+ if err != nil {
+ render.JSON(w, r, err)
+ render.Status(r, http.StatusInternalServerError)
+ return
+ }
+
+ for m := range mockChan {
+ select {
+ case <-r.Context().Done():
+ // Client closed the connection or context was cancelled
+ return
+ default:
+ render.JSON(w, r, m)
+ flusher.Flush()
+ }
+ }
+}
+
+func (a *AgentRequest) RegisterClients(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+
+ var registerReq models.RegisterReq
+ err := json.NewDecoder(r.Body).Decode(®isterReq)
+
+ register := models.AgentResp{
+ ClientID: 0,
+ Error: nil,
+ }
+
+ if err != nil {
+ register.Error = err
+ render.JSON(w, r, register)
+ render.Status(r, http.StatusBadRequest)
+ return
+ }
+
+ fmt.Printf("SetupRequest: %v\n", registerReq.SetupOptions.ClientNsPid)
+
+ if registerReq.SetupOptions.ClientNsPid == 0 {
+ register.Error = fmt.Errorf("Client pid is required")
+ render.JSON(w, r, register)
+ render.Status(r, http.StatusBadRequest)
+ return
+ }
+ fmt.Printf("Register Client req: %v\n", registerReq.SetupOptions)
+
+ err = a.agent.RegisterClient(r.Context(), registerReq.SetupOptions)
+ if err != nil {
+ register.Error = err
+ render.JSON(w, r, register)
+ render.Status(r, http.StatusInternalServerError)
+ return
+ }
+
+ render.JSON(w, r, register)
+ render.Status(r, http.StatusOK)
+}
diff --git a/pkg/agent/routes/replay.go b/pkg/agent/routes/replay.go
new file mode 100644
index 0000000000..58edccbe90
--- /dev/null
+++ b/pkg/agent/routes/replay.go
@@ -0,0 +1,95 @@
+package routes
+
+import (
+ "encoding/json"
+ "net/http"
+ "strconv"
+
+ "github.com/go-chi/render"
+ "go.keploy.io/server/v2/pkg/models"
+)
+
+func (a *AgentRequest) MockOutgoing(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+
+ var OutgoingReq models.OutgoingReq
+ err := json.NewDecoder(r.Body).Decode(&OutgoingReq)
+
+ mockRes := models.AgentResp{
+ ClientID: 0,
+ Error: nil,
+ IsSuccess: true,
+ }
+
+ if err != nil {
+ mockRes.Error = err
+ mockRes.IsSuccess = false
+ render.JSON(w, r, mockRes)
+ render.Status(r, http.StatusBadRequest)
+ return
+ }
+
+ err = a.agent.MockOutgoing(r.Context(), 0, OutgoingReq.OutgoingOptions)
+ if err != nil {
+ mockRes.Error = err
+ mockRes.IsSuccess = false
+ render.JSON(w, r, err)
+ render.Status(r, http.StatusInternalServerError)
+ return
+ }
+
+ render.JSON(w, r, mockRes)
+ render.Status(r, http.StatusOK)
+}
+
+func (a *AgentRequest) SetMocks(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+
+ var SetMocksReq models.SetMocksReq
+ err := json.NewDecoder(r.Body).Decode(&SetMocksReq)
+
+ setmockRes := models.AgentResp{
+ ClientID: 0,
+ Error: nil,
+ }
+ if err != nil {
+ setmockRes.Error = err
+ setmockRes.IsSuccess = false
+ render.JSON(w, r, err)
+ render.Status(r, http.StatusBadRequest)
+ return
+ }
+
+ err = a.agent.SetMocks(r.Context(), 0, SetMocksReq.Filtered, SetMocksReq.UnFiltered)
+ if err != nil {
+ setmockRes.Error = err
+ setmockRes.IsSuccess = false
+ render.JSON(w, r, err)
+ render.Status(r, http.StatusInternalServerError)
+ return
+ }
+
+ render.JSON(w, r, setmockRes)
+ render.Status(r, http.StatusOK)
+
+}
+
+func (a *AgentRequest) GetConsumedMocks(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+
+ appID := r.URL.Query().Get("id")
+
+ // convert string to uint64
+ appIDInt, err := strconv.ParseUint(appID, 10, 64)
+
+ consumedMocks, err := a.agent.GetConsumedMocks(r.Context(), appIDInt)
+ if err != nil {
+ render.JSON(w, r, err)
+ render.Status(r, http.StatusInternalServerError)
+ return
+ }
+
+ render.JSON(w, r, consumedMocks)
+ render.Status(r, http.StatusOK)
+
+}
diff --git a/pkg/agent/utils.go b/pkg/agent/utils.go
new file mode 100644
index 0000000000..75351409e0
--- /dev/null
+++ b/pkg/agent/utils.go
@@ -0,0 +1,2 @@
+package agent
+
diff --git a/pkg/core/app/app.go b/pkg/core/app/app.go
index 5f40049a04..ae24fee2cd 100644
--- a/pkg/core/app/app.go
+++ b/pkg/core/app/app.go
@@ -1,4 +1,4 @@
-//go:build linux
+//go:build !windows
// Package app provides functionality for managing applications.
package app
@@ -8,6 +8,7 @@ import (
"errors"
"fmt"
"os/exec"
+ "strings"
"syscall"
"time"
@@ -15,7 +16,6 @@ import (
"go.keploy.io/server/v2/pkg/models"
- "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters"
"go.keploy.io/server/v2/pkg/platform/docker"
@@ -52,7 +52,6 @@ type App struct {
keployNetwork string
keployContainer string
keployIPv4 string
- inodeChan chan uint64
EnableTesting bool
Mode models.Mode
}
@@ -65,6 +64,7 @@ type Options struct {
DockerNetwork string
}
+// Setup sets up the application for running.
func (a *App) Setup(_ context.Context) error {
if utils.IsDockerCmd(a.kind) && isDetachMode(a.logger, a.cmd, a.kind) {
@@ -117,6 +117,14 @@ func (a *App) SetupDocker() error {
utils.LogError(a.logger, err, fmt.Sprintf("failed to inject network:%v to the keploy container", a.containerNetwork))
return err
}
+
+ // attaching the init container's PID namespace to the app container
+ err = a.attachInitPid(context.Background())
+ if err != nil {
+ utils.LogError(a.logger, err, "failed to attach init pid")
+ return err
+ }
+
return nil
}
@@ -197,6 +205,13 @@ func (a *App) SetupCompose() error {
}
}
+ // adding keploy init pid to the compose file
+ err = a.docker.SetInitPid(compose)
+ if err != nil {
+ utils.LogError(a.logger, nil, "failed to set init pid in the compose file")
+ return err
+ }
+
if composeChanged {
err = a.docker.WriteComposeFile(compose, newPath)
if err != nil {
@@ -248,7 +263,7 @@ func (a *App) injectNetwork(network string) error {
//TODO: check the logic for correctness
for n, settings := range keployNetworks {
if n == network {
- a.keployIPv4 = settings.IPAddress
+ a.keployIPv4 = settings.IPAddress // TODO: keployIPv4 needs to be send to the agent
a.logger.Info("Successfully injected network to the keploy container", zap.Any("Keploy container", a.keployContainer), zap.Any("appNetwork", network), zap.String("keploy container ip", a.keployIPv4))
return nil
}
@@ -261,6 +276,28 @@ func (a *App) injectNetwork(network string) error {
return fmt.Errorf("failed to find the network:%v in the keploy container", network)
}
+// AttachInitPid modifies the existing Docker command to attach the init container's PID namespace
+func (a *App) attachInitPid(_ context.Context) error {
+ if a.cmd == "" {
+ return fmt.Errorf("no command provided to modify")
+ }
+
+ // Add the --pid=container: flag to the command
+ pidMode := fmt.Sprintf("--pid=container:%s", "keploy-init")
+ fmt.Println("pidMode:", pidMode)
+ // Inject the pidMode flag after 'docker run' in the command
+ parts := strings.SplitN(a.cmd, " ", 3) // Split by first two spaces to isolate "docker run"
+ if len(parts) < 3 {
+ return fmt.Errorf("invalid command structure: %s", a.cmd)
+ }
+
+ // Modify the command to insert the pidMode
+ a.cmd = fmt.Sprintf("%s %s %s %s", parts[0], parts[1], pidMode, parts[2])
+
+ fmt.Println("Modified command:", a.cmd)
+ return nil
+}
+
func (a *App) extractMeta(ctx context.Context, e events.Message) (bool, error) {
if e.Action != "start" {
return false, nil
@@ -290,8 +327,7 @@ func (a *App) extractMeta(ctx context.Context, e events.Message) (bool, error) {
return false, err
}
- a.inodeChan <- inode
- a.logger.Debug("container started and successfully extracted inode", zap.Any("inode", inode))
+ a.logger.Info("container started and successfully extracted inode", zap.Any("inode", inode))
if info.NetworkSettings == nil || info.NetworkSettings.Networks == nil {
a.logger.Debug("container network settings not available", zap.Any("containerDetails.NetworkSettings", info.NetworkSettings))
return false, nil
@@ -309,7 +345,6 @@ func (a *App) extractMeta(ctx context.Context, e events.Message) (bool, error) {
func (a *App) getDockerMeta(ctx context.Context) <-chan error {
// listen for the docker daemon events
defer a.logger.Debug("exiting from goroutine of docker daemon event listener")
-
errCh := make(chan error, 1)
timer := time.NewTimer(time.Duration(a.containerDelay) * time.Second)
logTicker := time.NewTicker(1 * time.Second)
@@ -323,7 +358,7 @@ func (a *App) getDockerMeta(ctx context.Context) <-chan error {
filters.KeyValuePair{Key: "action", Value: "start"},
)
- messages, errCh2 := a.docker.Events(ctx, types.EventsOptions{
+ messages, errCh2 := a.docker.Events(ctx, events.ListOptions{
Filters: eventFilter,
})
@@ -415,14 +450,14 @@ func (a *App) runDocker(ctx context.Context) models.AppError {
}
}
-func (a *App) Run(ctx context.Context, inodeChan chan uint64) models.AppError {
- a.inodeChan = inodeChan
+func (a *App) Run(ctx context.Context) models.AppError {
if utils.IsDockerCmd(a.kind) {
return a.runDocker(ctx)
}
return a.run(ctx)
}
+
func (a *App) waitTillExit() {
timeout := time.NewTimer(30 * time.Second)
logTicker := time.NewTicker(1 * time.Second)
@@ -464,7 +499,7 @@ func (a *App) run(ctx context.Context) models.AppError {
cmdCancel := func(cmd *exec.Cmd) func() error {
return func() error {
if utils.IsDockerCmd(a.kind) {
- a.logger.Debug("sending SIGINT to the container", zap.Any("cmd.Process.Pid", cmd.Process.Pid))
+ a.logger.Info("sending SIGINT to the app container", zap.Any("cmd.Process.Pid", cmd.Process.Pid))
err := utils.SendSignal(a.logger, -cmd.Process.Pid, syscall.SIGINT)
return err
}
diff --git a/pkg/core/app/util.go b/pkg/core/app/util.go
index 6d374fe159..12a8f02fc2 100644
--- a/pkg/core/app/util.go
+++ b/pkg/core/app/util.go
@@ -1,4 +1,4 @@
-//go:build linux
+//go:build !windows
package app
@@ -64,7 +64,7 @@ func modifyDockerComposeCommand(appCmd, newComposeFile string) string {
func getInode(pid int) (uint64, error) {
path := filepath.Join("/proc", strconv.Itoa(pid), "ns", "pid")
-
+
f, err := os.Stat(path)
if err != nil {
return 0, err
diff --git a/pkg/core/core_linux.go b/pkg/core/core_linux.go
deleted file mode 100644
index 1db78c78f7..0000000000
--- a/pkg/core/core_linux.go
+++ /dev/null
@@ -1,272 +0,0 @@
-//go:build linux
-
-// Package core provides functionality for managing core functionalities in Keploy.
-package core
-
-import (
- "context"
- "errors"
- "fmt"
- "sync"
-
- "golang.org/x/sync/errgroup"
-
- "go.keploy.io/server/v2/pkg/core/app"
- "go.keploy.io/server/v2/pkg/core/hooks/structs"
- "go.keploy.io/server/v2/pkg/models"
- "go.keploy.io/server/v2/pkg/platform/docker"
- "go.keploy.io/server/v2/utils"
- "go.uber.org/zap"
-)
-
-type Core struct {
- Proxy // embedding the Proxy interface to transfer the proxy methods to the core object
- Hooks // embedding the Hooks interface to transfer the hooks methods to the core object
- Tester // embedding the Tester interface to transfer the tester methods to the core object
- dockerClient docker.Client //embedding the docker client to transfer the docker client methods to the core object
- logger *zap.Logger
- id utils.AutoInc
- apps sync.Map
- proxyStarted bool
-}
-
-func New(logger *zap.Logger, hook Hooks, proxy Proxy, tester Tester, client docker.Client) *Core {
- return &Core{
- logger: logger,
- Hooks: hook,
- Proxy: proxy,
- Tester: tester,
- dockerClient: client,
- }
-}
-
-func (c *Core) Setup(ctx context.Context, cmd string, opts models.SetupOptions) (uint64, error) {
- // create a new app and store it in the map
- id := uint64(c.id.Next())
- a := app.NewApp(c.logger, id, cmd, c.dockerClient, app.Options{
- DockerNetwork: opts.DockerNetwork,
- Container: opts.Container,
- DockerDelay: opts.DockerDelay,
- })
- c.apps.Store(id, a)
-
- err := a.Setup(ctx)
- if err != nil {
- utils.LogError(c.logger, err, "failed to setup app")
- return 0, err
- }
- return id, nil
-}
-
-func (c *Core) getApp(id uint64) (*app.App, error) {
- a, ok := c.apps.Load(id)
- if !ok {
- return nil, fmt.Errorf("app with id:%v not found", id)
- }
-
- // type assertion on the app
- h, ok := a.(*app.App)
- if !ok {
- return nil, fmt.Errorf("failed to type assert app with id:%v", id)
- }
-
- return h, nil
-}
-
-func (c *Core) Hook(ctx context.Context, id uint64, opts models.HookOptions) error {
- hookErr := errors.New("failed to hook into the app")
-
- a, err := c.getApp(id)
- if err != nil {
- utils.LogError(c.logger, err, "failed to get app")
- return hookErr
- }
-
- isDocker := false
- appKind := a.Kind(ctx)
- //check if the app is docker/docker-compose or native
- if utils.IsDockerCmd(appKind) {
- isDocker = true
- }
-
- select {
- case <-ctx.Done():
- return ctx.Err()
- default:
- }
-
- g, ok := ctx.Value(models.ErrGroupKey).(*errgroup.Group)
- if !ok {
- return errors.New("failed to get the error group from the context")
- }
-
- // create a new error group for the hooks
- hookErrGrp, _ := errgroup.WithContext(ctx)
- hookCtx := context.WithoutCancel(ctx) //so that main context doesn't cancel the hookCtx to control the lifecycle of the hooks
- hookCtx, hookCtxCancel := context.WithCancel(hookCtx)
- hookCtx = context.WithValue(hookCtx, models.ErrGroupKey, hookErrGrp)
-
- // create a new error group for the proxy
- proxyErrGrp, _ := errgroup.WithContext(ctx)
- proxyCtx := context.WithoutCancel(ctx) //so that main context doesn't cancel the proxyCtx to control the lifecycle of the proxy
- proxyCtx, proxyCtxCancel := context.WithCancel(proxyCtx)
- proxyCtx = context.WithValue(proxyCtx, models.ErrGroupKey, proxyErrGrp)
-
- g.Go(func() error {
- <-ctx.Done()
-
- proxyCtxCancel()
- err = proxyErrGrp.Wait()
- if err != nil {
- utils.LogError(c.logger, err, "failed to stop the proxy")
- }
-
- hookCtxCancel()
- err := hookErrGrp.Wait()
- if err != nil {
- utils.LogError(c.logger, err, "failed to unload the hooks")
- }
-
- //deleting in order to free the memory in case of rerecord. otherwise different app id will be created for the same app.
- c.apps.Delete(id)
- c.id = utils.AutoInc{}
-
- return nil
- })
-
- //load hooks
- err = c.Hooks.Load(hookCtx, id, HookCfg{
- AppID: id,
- Pid: 0,
- IsDocker: isDocker,
- KeployIPV4: a.KeployIPv4Addr(),
- Mode: opts.Mode,
- Rules: opts.Rules,
- })
- if err != nil {
- utils.LogError(c.logger, err, "failed to load hooks")
- return hookErr
- }
-
- if c.proxyStarted {
- c.logger.Debug("Proxy already started")
- // return nil
- }
-
- select {
- case <-ctx.Done():
- return ctx.Err()
- default:
- }
-
- // TODO: Hooks can be loaded multiple times but proxy should be started only once
- // if there is another containerized app, then we need to pass new (ip:port) of proxy to the eBPF
- // as the network namespace is different for each container and so is the keploy/proxy IP to communicate with the app.
- // start proxy
- err = c.Proxy.StartProxy(proxyCtx, ProxyOptions{
- DNSIPv4Addr: a.KeployIPv4Addr(),
- //DnsIPv6Addr: ""
- })
- if err != nil {
- utils.LogError(c.logger, err, "failed to start proxy")
- return hookErr
- }
-
- c.proxyStarted = true
-
- // For keploy test bench
- if opts.EnableTesting {
-
- // enable testing in the app
- a.EnableTesting = true
- a.Mode = opts.Mode
-
- // Setting up the test bench
- err := c.Tester.Setup(ctx, models.TestingOptions{Mode: opts.Mode})
- if err != nil {
- utils.LogError(c.logger, err, "error while setting up the test bench environment")
- return errors.New("failed to setup the test bench")
- }
- }
-
- return nil
-}
-
-func (c *Core) Run(ctx context.Context, id uint64, _ models.RunOptions) models.AppError {
- a, err := c.getApp(id)
- if err != nil {
- utils.LogError(c.logger, err, "failed to get app")
- return models.AppError{AppErrorType: models.ErrInternal, Err: err}
- }
-
- runAppErrGrp, runAppCtx := errgroup.WithContext(ctx)
-
- inodeErrCh := make(chan error, 1)
- appErrCh := make(chan models.AppError, 1)
- inodeChan := make(chan uint64, 1) //send inode to the hook
-
- defer func() {
- err := runAppErrGrp.Wait()
- defer close(inodeErrCh)
- defer close(inodeChan)
- if err != nil {
- utils.LogError(c.logger, err, "failed to stop the app")
- }
- }()
-
- runAppErrGrp.Go(func() error {
- defer utils.Recover(c.logger)
- if a.Kind(ctx) == utils.Native {
- return nil
- }
- select {
- case inode := <-inodeChan:
- err := c.Hooks.SendDockerAppInfo(id, structs.DockerAppInfo{AppInode: inode, ClientID: id})
- if err != nil {
- utils.LogError(c.logger, err, "")
-
- inodeErrCh <- errors.New("failed to send inode to the kernel")
- }
- case <-ctx.Done():
- return nil
- }
- return nil
- })
-
- runAppErrGrp.Go(func() error {
- defer utils.Recover(c.logger)
- defer close(appErrCh)
- appErr := a.Run(runAppCtx, inodeChan)
- if appErr.Err != nil {
- utils.LogError(c.logger, appErr.Err, "error while running the app")
- appErrCh <- appErr
- }
- return nil
- })
-
- select {
- case <-runAppCtx.Done():
- return models.AppError{AppErrorType: models.ErrCtxCanceled, Err: nil}
- case appErr := <-appErrCh:
- return appErr
- case inodeErr := <-inodeErrCh:
- return models.AppError{AppErrorType: models.ErrInternal, Err: inodeErr}
- }
-}
-
-func (c *Core) GetContainerIP(_ context.Context, id uint64) (string, error) {
-
- a, err := c.getApp(id)
- if err != nil {
- utils.LogError(c.logger, err, "failed to get app")
- return "", err
- }
-
- ip := a.ContainerIPv4Addr()
- c.logger.Debug("ip address of the target app container", zap.Any("ip", ip))
- if ip == "" {
- return "", fmt.Errorf("failed to get the IP address of the app container. Try increasing --delay (in seconds)")
- }
-
- return ip, nil
-}
diff --git a/pkg/core/hooks/bpf_arm64_bpfel.go b/pkg/core/hooks/bpf_arm64_bpfel.go
index df9cc8dbec..af17c81b4a 100644
--- a/pkg/core/hooks/bpf_arm64_bpfel.go
+++ b/pkg/core/hooks/bpf_arm64_bpfel.go
@@ -95,11 +95,11 @@ type bpfMapSpecs struct {
ConnInfoMap *ebpf.MapSpec `ebpf:"conn_info_map"`
CurrentSockMap *ebpf.MapSpec `ebpf:"current_sock_map"`
DestInfoMap *ebpf.MapSpec `ebpf:"dest_info_map"`
- DockerAppRegistrationMap *ebpf.MapSpec `ebpf:"docker_app_registration_map"`
KeployAgentKernelPidMap *ebpf.MapSpec `ebpf:"keploy_agent_kernel_pid_map"`
KeployAgentRegistrationMap *ebpf.MapSpec `ebpf:"keploy_agent_registration_map"`
KeployClientKernelPidMap *ebpf.MapSpec `ebpf:"keploy_client_kernel_pid_map"`
KeployClientRegistrationMap *ebpf.MapSpec `ebpf:"keploy_client_registration_map"`
+ KeployProxyInfo *ebpf.MapSpec `ebpf:"keploy_proxy_info"`
RedirectProxyMap *ebpf.MapSpec `ebpf:"redirect_proxy_map"`
SocketCloseEvents *ebpf.MapSpec `ebpf:"socket_close_events"`
SocketDataEventBufferHeap *ebpf.MapSpec `ebpf:"socket_data_event_buffer_heap"`
@@ -135,11 +135,11 @@ type bpfMaps struct {
ConnInfoMap *ebpf.Map `ebpf:"conn_info_map"`
CurrentSockMap *ebpf.Map `ebpf:"current_sock_map"`
DestInfoMap *ebpf.Map `ebpf:"dest_info_map"`
- DockerAppRegistrationMap *ebpf.Map `ebpf:"docker_app_registration_map"`
KeployAgentKernelPidMap *ebpf.Map `ebpf:"keploy_agent_kernel_pid_map"`
KeployAgentRegistrationMap *ebpf.Map `ebpf:"keploy_agent_registration_map"`
KeployClientKernelPidMap *ebpf.Map `ebpf:"keploy_client_kernel_pid_map"`
KeployClientRegistrationMap *ebpf.Map `ebpf:"keploy_client_registration_map"`
+ KeployProxyInfo *ebpf.Map `ebpf:"keploy_proxy_info"`
RedirectProxyMap *ebpf.Map `ebpf:"redirect_proxy_map"`
SocketCloseEvents *ebpf.Map `ebpf:"socket_close_events"`
SocketDataEventBufferHeap *ebpf.Map `ebpf:"socket_data_event_buffer_heap"`
@@ -158,11 +158,11 @@ func (m *bpfMaps) Close() error {
m.ConnInfoMap,
m.CurrentSockMap,
m.DestInfoMap,
- m.DockerAppRegistrationMap,
m.KeployAgentKernelPidMap,
m.KeployAgentRegistrationMap,
m.KeployClientKernelPidMap,
m.KeployClientRegistrationMap,
+ m.KeployProxyInfo,
m.RedirectProxyMap,
m.SocketCloseEvents,
m.SocketDataEventBufferHeap,
diff --git a/pkg/core/hooks/bpf_arm64_bpfel.o b/pkg/core/hooks/bpf_arm64_bpfel.o
index f8c9fe223f..b7f61342eb 100644
Binary files a/pkg/core/hooks/bpf_arm64_bpfel.o and b/pkg/core/hooks/bpf_arm64_bpfel.o differ
diff --git a/pkg/core/hooks/bpf_x86_bpfel.go b/pkg/core/hooks/bpf_x86_bpfel.go
index cf8f135116..99ef0483c7 100644
--- a/pkg/core/hooks/bpf_x86_bpfel.go
+++ b/pkg/core/hooks/bpf_x86_bpfel.go
@@ -95,11 +95,11 @@ type bpfMapSpecs struct {
ConnInfoMap *ebpf.MapSpec `ebpf:"conn_info_map"`
CurrentSockMap *ebpf.MapSpec `ebpf:"current_sock_map"`
DestInfoMap *ebpf.MapSpec `ebpf:"dest_info_map"`
- DockerAppRegistrationMap *ebpf.MapSpec `ebpf:"docker_app_registration_map"`
KeployAgentKernelPidMap *ebpf.MapSpec `ebpf:"keploy_agent_kernel_pid_map"`
KeployAgentRegistrationMap *ebpf.MapSpec `ebpf:"keploy_agent_registration_map"`
KeployClientKernelPidMap *ebpf.MapSpec `ebpf:"keploy_client_kernel_pid_map"`
KeployClientRegistrationMap *ebpf.MapSpec `ebpf:"keploy_client_registration_map"`
+ KeployProxyInfo *ebpf.MapSpec `ebpf:"keploy_proxy_info"`
RedirectProxyMap *ebpf.MapSpec `ebpf:"redirect_proxy_map"`
SocketCloseEvents *ebpf.MapSpec `ebpf:"socket_close_events"`
SocketDataEventBufferHeap *ebpf.MapSpec `ebpf:"socket_data_event_buffer_heap"`
@@ -135,11 +135,11 @@ type bpfMaps struct {
ConnInfoMap *ebpf.Map `ebpf:"conn_info_map"`
CurrentSockMap *ebpf.Map `ebpf:"current_sock_map"`
DestInfoMap *ebpf.Map `ebpf:"dest_info_map"`
- DockerAppRegistrationMap *ebpf.Map `ebpf:"docker_app_registration_map"`
KeployAgentKernelPidMap *ebpf.Map `ebpf:"keploy_agent_kernel_pid_map"`
KeployAgentRegistrationMap *ebpf.Map `ebpf:"keploy_agent_registration_map"`
KeployClientKernelPidMap *ebpf.Map `ebpf:"keploy_client_kernel_pid_map"`
KeployClientRegistrationMap *ebpf.Map `ebpf:"keploy_client_registration_map"`
+ KeployProxyInfo *ebpf.Map `ebpf:"keploy_proxy_info"`
RedirectProxyMap *ebpf.Map `ebpf:"redirect_proxy_map"`
SocketCloseEvents *ebpf.Map `ebpf:"socket_close_events"`
SocketDataEventBufferHeap *ebpf.Map `ebpf:"socket_data_event_buffer_heap"`
@@ -158,11 +158,11 @@ func (m *bpfMaps) Close() error {
m.ConnInfoMap,
m.CurrentSockMap,
m.DestInfoMap,
- m.DockerAppRegistrationMap,
m.KeployAgentKernelPidMap,
m.KeployAgentRegistrationMap,
m.KeployClientKernelPidMap,
m.KeployClientRegistrationMap,
+ m.KeployProxyInfo,
m.RedirectProxyMap,
m.SocketCloseEvents,
m.SocketDataEventBufferHeap,
diff --git a/pkg/core/hooks/bpf_x86_bpfel.o b/pkg/core/hooks/bpf_x86_bpfel.o
index f3f76af725..3a45e1dfba 100644
Binary files a/pkg/core/hooks/bpf_x86_bpfel.o and b/pkg/core/hooks/bpf_x86_bpfel.o differ
diff --git a/pkg/core/hooks/conn/socket.go b/pkg/core/hooks/conn/socket.go
index d7af88596f..edae6a16c3 100644
--- a/pkg/core/hooks/conn/socket.go
+++ b/pkg/core/hooks/conn/socket.go
@@ -38,6 +38,7 @@ func ListenSocket(ctx context.Context, l *zap.Logger, openMap, dataMap, closeMap
if !ok {
return nil, errors.New("failed to get the error group from the context")
}
+
g.Go(func() error {
defer utils.Recover(l)
go func() {
diff --git a/pkg/core/hooks/hooks.go b/pkg/core/hooks/hooks.go
index 2f589abf69..261d7cc0d0 100644
--- a/pkg/core/hooks/hooks.go
+++ b/pkg/core/hooks/hooks.go
@@ -28,6 +28,9 @@ import (
)
func NewHooks(logger *zap.Logger, cfg *config.Config) *Hooks {
+ if cfg.Agent.IsDocker {
+ cfg.ProxyPort = 36789
+ }
return &Hooks{
logger: logger,
sess: core.NewSessions(),
@@ -49,10 +52,11 @@ type Hooks struct {
m sync.Mutex
// eBPF C shared maps
- clientRegistrationMap *ebpf.Map
- agentRegistartionMap *ebpf.Map
- dockerAppRegistrationMap *ebpf.Map
- redirectProxyMap *ebpf.Map
+ clientRegistrationMap *ebpf.Map
+ agentRegistartionMap *ebpf.Map
+
+ redirectProxyMap *ebpf.Map
+ proxyInfoMap *ebpf.Map
//--------------
// eBPF C shared objectsobjects
@@ -96,7 +100,7 @@ func (h *Hooks) Load(ctx context.Context, id uint64, opts core.HookCfg) error {
ID: id,
})
- err := h.load(ctx, opts)
+ err := h.load(opts)
if err != nil {
return err
}
@@ -119,7 +123,7 @@ func (h *Hooks) Load(ctx context.Context, id uint64, opts core.HookCfg) error {
return nil
}
-func (h *Hooks) load(ctx context.Context, opts core.HookCfg) error {
+func (h *Hooks) load(opts core.HookCfg) error {
// Allow the current process to lock memory for eBPF resources.
if err := rlimit.RemoveMemlock(); err != nil {
utils.LogError(h.logger, err, "failed to lock memory for eBPF resources")
@@ -142,7 +146,8 @@ func (h *Hooks) load(ctx context.Context, opts core.HookCfg) error {
h.redirectProxyMap = objs.RedirectProxyMap
h.clientRegistrationMap = objs.KeployClientRegistrationMap
h.agentRegistartionMap = objs.KeployAgentRegistrationMap
- h.dockerAppRegistrationMap = objs.DockerAppRegistrationMap
+ h.proxyInfoMap = objs.KeployProxyInfo
+
h.objects = objs
// ---------------
@@ -408,28 +413,17 @@ func (h *Hooks) load(ctx context.Context, opts core.HookCfg) error {
h.logger.Info("keploy initialized and probes added to the kernel.")
- var clientInfo structs.ClientInfo = structs.ClientInfo{}
-
- switch opts.Mode {
- case models.MODE_RECORD:
- clientInfo.Mode = uint32(1)
- case models.MODE_TEST:
- clientInfo.Mode = uint32(2)
- default:
- clientInfo.Mode = uint32(0)
- }
-
//sending keploy pid to kernel to get filtered
- inode, err := getSelfInodeNumber()
- if err != nil {
- utils.LogError(h.logger, err, "failed to get inode of the keploy process")
- return err
- }
+ // inode, err := getSelfInodeNumber()
+ // if err != nil {
+ // utils.LogError(h.logger, err, "failed to get inode of the keploy process")
+ // return err
+ // }
- clientInfo.KeployClientInode = inode
- clientInfo.KeployClientNsPid = uint32(os.Getpid())
- clientInfo.IsKeployClientRegistered = uint32(0)
- h.logger.Debug("Keploy Pid sent successfully...")
+ // clientInfo.KeployClientInode = inode
+ // clientInfo.KeployClientNsPid = uint32(os.Getpid())
+ // clientInfo.IsKeployClientRegistered = uint32(0)
+ // h.logger.Info("Keploy Pid sent successfully...")
if opts.IsDocker {
h.proxyIP4 = opts.KeployIPV4
@@ -443,41 +437,38 @@ func (h *Hooks) load(ctx context.Context, opts core.HookCfg) error {
h.logger.Debug("proxy ips", zap.String("ipv4", h.proxyIP4), zap.Any("ipv6", h.proxyIP6))
- proxyIP, err := IPv4ToUint32(h.proxyIP4)
- if err != nil {
- return fmt.Errorf("failed to convert ip string:[%v] to 32-bit integer", opts.KeployIPV4)
- }
-
var agentInfo structs.AgentInfo = structs.AgentInfo{}
-
- agentInfo.ProxyInfo = structs.ProxyInfo{
- IP4: proxyIP,
- IP6: h.proxyIP6,
- Port: h.proxyPort,
+ agentInfo.KeployAgentNsPid = uint32(os.Getpid())
+ agentInfo.KeployAgentInode, err = GetSelfInodeNumber()
+ if err != nil {
+ utils.LogError(h.logger, err, "failed to get inode of the keploy process")
+ return err
}
agentInfo.DNSPort = int32(h.dnsPort)
- if opts.IsDocker {
- clientInfo.IsDockerApp = uint32(1)
- } else {
- clientInfo.IsDockerApp = uint32(0)
- }
-
- ports := GetPortToSendToKernel(ctx, opts.Rules)
- for i := 0; i < 10; i++ {
- if len(ports) <= i {
- clientInfo.PassThroughPorts[i] = -1
- continue
- }
- clientInfo.PassThroughPorts[i] = int32(ports[i])
- }
-
- err = h.SendClientInfo(opts.AppID, clientInfo)
- if err != nil {
- h.logger.Error("failed to send app info to the ebpf program", zap.Error(err))
- return err
- }
+ // if opts.IsDocker {
+ // clientInfo.IsDockerApp = uint32(1)
+ // } else {
+ // clientInfo.IsDockerApp = uint32(0)
+ // }
+
+ // ports := GetPortToSendToKernel(ctx, opts.Rules)
+ // for i := 0; i < 10; i++ {
+ // if len(ports) <= i {
+ // clientInfo.PassThroughPorts[i] = -1
+ // continue
+ // }
+ // clientInfo.PassThroughPorts[i] = int32(ports[i])
+ // }
+
+ // for sending client pid to kernel
+ // fmt.Println("Sending client info to kernel...", clientInfo)
+ // err = h.SendClientInfo(opts.AppID, clientInfo)
+ // if err != nil {
+ // h.logger.Error("failed to send app info to the ebpf program", zap.Error(err))
+ // return err
+ // }
err = h.SendAgentInfo(agentInfo)
if err != nil {
h.logger.Error("failed to send agent info to the ebpf program", zap.Error(err))
@@ -491,9 +482,33 @@ func (h *Hooks) Record(ctx context.Context, _ uint64, opts models.IncomingOption
// TODO use the session to get the app id
// and then use the app id to get the test cases chan
// and pass that to eBPF consumers/listeners
+ fmt.Println("Recording hooks...")
return conn.ListenSocket(ctx, h.logger, h.objects.SocketOpenEvents, h.objects.SocketDataEvents, h.objects.SocketCloseEvents, opts)
}
+func (h *Hooks) SendKeployClientInfo(ctx context.Context, clientID uint64, clientInfo structs.ClientInfo) error {
+ // TODO use the session to get the app id
+ // and then use the app id to get the test cases chan
+ // and pass that to eBPF consumers/listeners
+
+ err := h.SendClientInfo(clientID, clientInfo)
+ if err != nil {
+ h.logger.Error("failed to send app info to the ebpf program", zap.Error(err))
+ return err
+ }
+
+ return nil
+}
+
+func (h *Hooks) SendClientProxyInfo(ctx context.Context, clientID uint64, proxyInfo structs.ProxyInfo) error {
+ err := h.SendProxyInfo(clientID, proxyInfo)
+ if err != nil {
+ h.logger.Error("failed to send app info to the ebpf program", zap.Error(err))
+ return err
+ }
+ return nil
+}
+
func (h *Hooks) unLoad(_ context.Context) {
// closing all events
//other
diff --git a/pkg/core/hooks/kernelComm.go b/pkg/core/hooks/kernelComm.go
index abf8d98d07..acdcb717aa 100644
--- a/pkg/core/hooks/kernelComm.go
+++ b/pkg/core/hooks/kernelComm.go
@@ -6,8 +6,6 @@ import (
"context"
"fmt"
- "math/rand"
-
"github.com/cilium/ebpf"
"go.keploy.io/server/v2/pkg/core"
"go.keploy.io/server/v2/pkg/core/hooks/structs"
@@ -74,30 +72,21 @@ func (h *Hooks) SendClientInfo(id uint64, appInfo structs.ClientInfo) error {
return nil
}
-func (h *Hooks) SendAgentInfo(agentInfo structs.AgentInfo) error {
- key := 0
- err := h.agentRegistartionMap.Update(uint32(key), agentInfo, ebpf.UpdateAny)
+// SendProxyInfo sends the network information to the kernel
+func (h *Hooks) SendProxyInfo(id uint64, proxInfo structs.ProxyInfo) error {
+ err := h.proxyInfoMap.Update(id, proxInfo, ebpf.UpdateAny)
if err != nil {
- utils.LogError(h.logger, err, "failed to send the agent info to the ebpf program")
+ utils.LogError(h.logger, err, "failed to send the proxy info to the ebpf program")
return err
}
return nil
}
-func (h *Hooks) SendDockerAppInfo(_ uint64, dockerAppInfo structs.DockerAppInfo) error {
- if h.appID != 0 {
- err := h.dockerAppRegistrationMap.Delete(h.appID)
- if err != nil {
- utils.LogError(h.logger, err, "failed to remove entry from dockerAppRegistrationMap")
- return err
- }
- }
- r := rand.New(rand.NewSource(rand.Int63()))
- randomNum := r.Uint64()
- h.appID = randomNum
- err := h.dockerAppRegistrationMap.Update(h.appID, dockerAppInfo, ebpf.UpdateAny)
+func (h *Hooks) SendAgentInfo(agentInfo structs.AgentInfo) error {
+ key := 0
+ err := h.agentRegistartionMap.Update(uint32(key), agentInfo, ebpf.UpdateAny)
if err != nil {
- utils.LogError(h.logger, err, "failed to send the dockerAppInfo info to the ebpf program")
+ utils.LogError(h.logger, err, "failed to send the agent info to the ebpf program")
return err
}
return nil
diff --git a/pkg/core/hooks/structs/structs.go b/pkg/core/hooks/structs/structs.go
index 3e9483d203..1df815c2c7 100755
--- a/pkg/core/hooks/structs/structs.go
+++ b/pkg/core/hooks/structs/structs.go
@@ -5,14 +5,6 @@ package structs
type BpfSpinLock struct{ Val uint32 }
-// struct dest_info_t
-// {
-// u32 ip_version;
-// u32 dest_ip4;
-// u32 dest_ip6[4];
-// u32 dest_port;
-// u32 kernelPid;
-// };
type DestInfo struct {
IPVersion uint32
@@ -23,12 +15,6 @@ type DestInfo struct {
ClientID uint64
}
-// struct proxy_info
-// {
-// u32 ip4;
-// u32 ip6[4];
-// u32 port;
-// };
type ProxyInfo struct {
IP4 uint32
@@ -41,16 +27,6 @@ type DockerAppInfo struct {
ClientID uint64
}
-// struct app_info
-// {
-// u32 keploy_client_ns_pid;
-// u64 keploy_client_inode;
-// u64 app_inode;
-// u32 mode;
-// u32 is_docker_app;
-// u32 is_keploy_client_registered; // whether the client is registered or not
-// s32 pass_through_ports[PASS_THROUGH_ARRAY_SIZE];
-// };
type ClientInfo struct {
KeployClientInode uint64
@@ -59,19 +35,12 @@ type ClientInfo struct {
IsDockerApp uint32
IsKeployClientRegistered uint32
PassThroughPorts [10]int32
+ AppInode uint64
}
-// struct agent_info
-// {
-// u32 keploy_agent_ns_pid;
-// u32 keploy_agent_inode;
-// struct proxy_info proxy_info;
-// s32 dns_port;
-// };
type AgentInfo struct {
KeployAgentNsPid uint32
DNSPort int32
KeployAgentInode uint64
- ProxyInfo ProxyInfo
}
diff --git a/pkg/core/hooks/util.go b/pkg/core/hooks/util.go
index f28a9ff485..4c315941df 100644
--- a/pkg/core/hooks/util.go
+++ b/pkg/core/hooks/util.go
@@ -1,4 +1,4 @@
-//go:build linux
+//go:build !windows
package hooks
@@ -97,7 +97,7 @@ func detectCgroupPath(logger *zap.Logger) (string, error) {
return "", errors.New("cgroup2 not mounted")
}
-func getSelfInodeNumber() (uint64, error) {
+func GetSelfInodeNumber() (uint64, error) {
p := filepath.Join("/proc", "self", "ns", "pid")
f, err := os.Stat(p)
diff --git a/pkg/core/proxy/integrations/mongo/mongo.go b/pkg/core/proxy/integrations/mongo/mongo.go
index fefe6fea42..044f0d0b87 100644
--- a/pkg/core/proxy/integrations/mongo/mongo.go
+++ b/pkg/core/proxy/integrations/mongo/mongo.go
@@ -82,6 +82,7 @@ func (m *Mongo) MockOutgoing(ctx context.Context, src net.Conn, dstCfg *integrat
return err
}
+ m.logger.Info("Mocking the mongo message")
// converts the yaml string into the binary packet
err = decodeMongo(ctx, logger, reqBuf, src, dstCfg, mockDb, opts)
if err != nil {
diff --git a/pkg/core/proxy/integrations/mysql/replayer/conn.go b/pkg/core/proxy/integrations/mysql/replayer/conn.go
index 5391fea307..c5832b80dd 100644
--- a/pkg/core/proxy/integrations/mysql/replayer/conn.go
+++ b/pkg/core/proxy/integrations/mysql/replayer/conn.go
@@ -91,9 +91,9 @@ func simulateInitialHandshake(ctx context.Context, logger *zap.Logger, clientCon
utils.LogError(logger, nil, "failed to assert mock handshake response packet")
return nil
}
-
+ // fmt.Println("Matching handshake response Database string...", req[0].Message.(*mysql.HandshakeResponse41Packet).Database)
// Match the handshake response from the client with the mock
- logger.Debug("matching handshake response", zap.Any("actual", pkt), zap.Any("mock", req[0].PacketBundle))
+ logger.Info("matching handshake response", zap.Any("actual", pkt), zap.Any("mock", req[0].PacketBundle))
err = matchHanshakeResponse41(ctx, logger, req[0].PacketBundle, *pkt)
if err != nil {
utils.LogError(logger, err, "error while matching handshakeResponse41")
diff --git a/pkg/core/proxy/integrations/mysql/wire/phase/conn/handshakeResponse41Packet.go b/pkg/core/proxy/integrations/mysql/wire/phase/conn/handshakeResponse41Packet.go
index 59d8b51a26..8d841e567a 100644
--- a/pkg/core/proxy/integrations/mysql/wire/phase/conn/handshakeResponse41Packet.go
+++ b/pkg/core/proxy/integrations/mysql/wire/phase/conn/handshakeResponse41Packet.go
@@ -10,6 +10,7 @@ import (
"fmt"
"go.keploy.io/server/v2/pkg/core/proxy/integrations/mysql/utils"
+ "go.keploy.io/server/v2/pkg/core/proxy/integrations/util"
"go.keploy.io/server/v2/pkg/models/mysql"
"go.uber.org/zap"
)
@@ -63,7 +64,7 @@ func DecodeHandshakeResponse41(_ context.Context, _ *zap.Logger, data []byte) (*
if packet.CapabilityFlags&mysql.CLIENT_CONNECT_WITH_DB != 0 {
idx = bytes.IndexByte(data, 0x00)
if idx != -1 {
- packet.Database = string(data[:idx])
+ packet.Database = util.EncodeBase64(data[:idx])
data = data[idx+1:]
}
}
@@ -176,7 +177,9 @@ func EncodeHandshakeResponse41(_ context.Context, _ *zap.Logger, packet *mysql.H
// Write Database
if packet.CapabilityFlags&mysql.CLIENT_CONNECT_WITH_DB != 0 {
- if _, err := buf.WriteString(packet.Database); err != nil {
+ connStr, _ := util.DecodeBase64(packet.Database)
+ fmt.Println("CONNECTION STRING", string(connStr))
+ if _, err := buf.WriteString(string(connStr)); err != nil {
return nil, fmt.Errorf("failed to write Database for HandshakeResponse41Packet: %w", err)
}
if err := buf.WriteByte(0x00); err != nil {
diff --git a/pkg/core/proxy/integrations/mysql/wire/phase/query/rowscols/binaryProtocolRowPacket.go b/pkg/core/proxy/integrations/mysql/wire/phase/query/rowscols/binaryProtocolRowPacket.go
index a985fc62ac..04df569ff7 100644
--- a/pkg/core/proxy/integrations/mysql/wire/phase/query/rowscols/binaryProtocolRowPacket.go
+++ b/pkg/core/proxy/integrations/mysql/wire/phase/query/rowscols/binaryProtocolRowPacket.go
@@ -255,13 +255,26 @@ func EncodeBinaryRow(_ context.Context, logger *zap.Logger, row *mysql.BinaryRow
columnEntry := row.Values[i]
+ fmt.Printf("ColumnEntry: %+v\n", columnEntry)
+ fmt.Printf("columnEntry.Value: %+v\n", columnEntry.Value)
+ fmt.Printf("Type of Value: %T\n", columnEntry.Value)
+
switch columnEntry.Type {
case mysql.FieldTypeLong:
var val any
if columnEntry.Unsigned {
- val = uint32(columnEntry.Value.(int))
+ if floatVal, ok := columnEntry.Value.(float64); ok {
+ columnEntry.Value = int64(floatVal)
+ } else {
+ val = uint32(columnEntry.Value.(int))
+ }
+
} else {
- val = int32(columnEntry.Value.(int))
+ if floatVal, ok := columnEntry.Value.(float64); ok {
+ columnEntry.Value = int64(floatVal)
+ } else {
+ val = int32(columnEntry.Value.(int))
+ }
}
if err := binary.Write(buf, binary.LittleEndian, val); err != nil {
return nil, fmt.Errorf("failed to write %T value: %w", val, err)
@@ -277,9 +290,19 @@ func EncodeBinaryRow(_ context.Context, logger *zap.Logger, row *mysql.BinaryRow
case mysql.FieldTypeTiny:
var val any
if columnEntry.Unsigned {
- val = uint8(columnEntry.Value.(int))
+ if floatVal, ok := columnEntry.Value.(float64); ok {
+ columnEntry.Value = int64(floatVal)
+ } else {
+ val = uint8(columnEntry.Value.(int))
+ }
+
} else {
- val = int8(columnEntry.Value.(int))
+ if floatVal, ok := columnEntry.Value.(float64); ok {
+ columnEntry.Value = int64(floatVal)
+ } else {
+ val = int8(columnEntry.Value.(int))
+ }
+
}
if err := binary.Write(buf, binary.LittleEndian, val); err != nil {
return nil, fmt.Errorf("failed to write %T value: %w", val, err)
@@ -288,9 +311,19 @@ func EncodeBinaryRow(_ context.Context, logger *zap.Logger, row *mysql.BinaryRow
case mysql.FieldTypeShort, mysql.FieldTypeYear:
var val any
if columnEntry.Unsigned {
- val = uint16(columnEntry.Value.(int))
+ if floatVal, ok := columnEntry.Value.(float64); ok {
+ columnEntry.Value = int64(floatVal)
+ } else {
+ val = uint16(columnEntry.Value.(int))
+ }
+
} else {
- val = int16(columnEntry.Value.(int))
+ if floatVal, ok := columnEntry.Value.(float64); ok {
+ columnEntry.Value = int64(floatVal)
+ } else {
+ val = int16(columnEntry.Value.(int))
+ }
+
}
if err := binary.Write(buf, binary.LittleEndian, val); err != nil {
return nil, fmt.Errorf("failed to write int16 value: %w", err)
@@ -298,11 +331,19 @@ func EncodeBinaryRow(_ context.Context, logger *zap.Logger, row *mysql.BinaryRow
case mysql.FieldTypeLongLong:
var val any
if columnEntry.Unsigned {
- val = uint64(columnEntry.Value.(int))
+ if floatVal, ok := columnEntry.Value.(float64); ok {
+ columnEntry.Value = int64(floatVal)
+ } else {
+ val = uint64(columnEntry.Value.(int))
+ }
} else {
- val = int64(columnEntry.Value.(int))
- }
+ if floatVal, ok := columnEntry.Value.(float64); ok {
+ columnEntry.Value = int64(floatVal)
+ } else {
+ val = int64(columnEntry.Value.(int))
+ }
+ }
if err := binary.Write(buf, binary.LittleEndian, val); err != nil {
return nil, fmt.Errorf("failed to write %T value: %w", val, err)
}
diff --git a/pkg/core/proxy/integrations/postgres/v1/decode.go b/pkg/core/proxy/integrations/postgres/v1/decode.go
index cc395af644..224b380458 100644
--- a/pkg/core/proxy/integrations/postgres/v1/decode.go
+++ b/pkg/core/proxy/integrations/postgres/v1/decode.go
@@ -67,7 +67,7 @@ func decodePostgres(ctx context.Context, logger *zap.Logger, reqBuf []byte, clie
}
if !matched {
- logger.Debug("MISMATCHED REQ is" + string(pgRequests[0]))
+ logger.Info("MISMATCHED REQ is" + string(pgRequests[0]))
_, err = pUtil.PassThrough(ctx, logger, clientConn, dstCfg, pgRequests)
if err != nil {
utils.LogError(logger, err, "failed to pass the request", zap.Any("request packets", len(pgRequests)))
diff --git a/pkg/core/proxy/integrations/postgres/v1/encode.go b/pkg/core/proxy/integrations/postgres/v1/encode.go
index 90b0d739d6..dda0c72208 100755
--- a/pkg/core/proxy/integrations/postgres/v1/encode.go
+++ b/pkg/core/proxy/integrations/postgres/v1/encode.go
@@ -329,7 +329,6 @@ func encodePostgres(ctx context.Context, logger *zap.Logger, reqBuf []byte, clie
PacketTypes: pg.FrontendWrapper.PacketTypes,
Identfier: "ServerResponse",
Length: uint32(len(reqBuf)),
- // Payload: bufStr,
AuthenticationOk: pg.FrontendWrapper.AuthenticationOk,
AuthenticationCleartextPassword: pg.FrontendWrapper.AuthenticationCleartextPassword,
AuthenticationMD5Password: pg.FrontendWrapper.AuthenticationMD5Password,
@@ -345,8 +344,8 @@ func encodePostgres(ctx context.Context, logger *zap.Logger, reqBuf []byte, clie
CommandCompletes: pg.FrontendWrapper.CommandCompletes,
CopyData: pg.FrontendWrapper.CopyData,
CopyDone: pg.FrontendWrapper.CopyDone,
- CopyInResponse: pg.FrontendWrapper.CopyInResponse,
- CopyOutResponse: pg.FrontendWrapper.CopyOutResponse,
+ // CopyInResponse: pg.FrontendWrapper.CopyInResponse,
+ // CopyOutResponse: pg.FrontendWrapper.CopyOutResponse,
DataRow: pg.FrontendWrapper.DataRow,
DataRows: pg.FrontendWrapper.DataRows,
EmptyQueryResponse: pg.FrontendWrapper.EmptyQueryResponse,
diff --git a/pkg/core/proxy/integrations/postgres/v1/match.go b/pkg/core/proxy/integrations/postgres/v1/match.go
index 819a9bd35d..ffe14acadc 100644
--- a/pkg/core/proxy/integrations/postgres/v1/match.go
+++ b/pkg/core/proxy/integrations/postgres/v1/match.go
@@ -93,7 +93,6 @@ func matchingReadablePG(ctx context.Context, logger *zap.Logger, mutex *sync.Mut
reqGoingOn := decodePgRequest(requestBuffers[0], logger)
if reqGoingOn != nil {
logger.Debug("PacketTypes", zap.Any("PacketTypes", reqGoingOn.PacketTypes))
- // fmt.Println("REQUEST GOING ON - ", reqGoingOn)
logger.Debug("ConnectionId-", zap.String("ConnectionId", ConnectionID))
logger.Debug("TestMap*****", zap.Any("TestMap", testmap))
}
@@ -142,7 +141,7 @@ func matchingReadablePG(ctx context.Context, logger *zap.Logger, mutex *sync.Mut
}
return true, []models.Frontend{ssl}, nil
case initMock.Spec.PostgresRequests[requestIndex].Identfier == "StartupRequest" && isStartupPacket(reqBuff) && initMock.Spec.PostgresRequests[requestIndex].Payload != "AAAACATSFi8=" && initMock.Spec.PostgresResponses[requestIndex].AuthType == 10:
- logger.Debug("CHANGING TO MD5 for Response", zap.String("mock", initMock.Name), zap.String("Req", bufStr))
+ logger.Info("CHANGING TO MD5 for Response", zap.String("mock", initMock.Name), zap.String("Req", bufStr))
res := make([]models.Frontend, len(initMock.Spec.PostgresResponses))
copy(res, initMock.Spec.PostgresResponses)
res[requestIndex].AuthType = 5
@@ -152,7 +151,7 @@ func matchingReadablePG(ctx context.Context, logger *zap.Logger, mutex *sync.Mut
}
return true, res, nil
case len(encodedMock) > 0 && encodedMock[0] == 'p' && initMock.Spec.PostgresRequests[requestIndex].PacketTypes[0] == "p" && reqBuff[0] == 'p':
- logger.Debug("CHANGING TO MD5 for Request and Response", zap.String("mock", initMock.Name), zap.String("Req", bufStr))
+ logger.Info("CHANGING TO MD5 for Request and Response", zap.String("mock", initMock.Name), zap.String("Req", bufStr))
res := make([]models.Frontend, len(initMock.Spec.PostgresResponses))
copy(res, initMock.Spec.PostgresResponses)
@@ -223,7 +222,7 @@ func matchingReadablePG(ctx context.Context, logger *zap.Logger, mutex *sync.Mut
getTestPS(requestBuffers, logger, ConnectionID)
}
- logger.Debug("Sorted Mocks inside pg parser: ", zap.Any("Len of sortedTcsMocks", len(sortedTcsMocks)))
+ logger.Info("Sorted Mocks inside pg parser: ", zap.Any("Len of sortedTcsMocks", len(sortedTcsMocks)))
var matched, sorted bool
var idx int
@@ -238,7 +237,7 @@ func matchingReadablePG(ctx context.Context, logger *zap.Logger, mutex *sync.Mut
if newMock != nil {
matchedMock = newMock
}
- logger.Debug("Matched In Sorted PG Matching Stream", zap.String("mock", matchedMock.Name))
+ logger.Info("Matched In Sorted PG Matching Stream", zap.String("mock", matchedMock.Name))
}
idx = findBinaryStreamMatch(logger, sortedTcsMocks, requestBuffers, sorted)
@@ -257,7 +256,7 @@ func matchingReadablePG(ctx context.Context, logger *zap.Logger, mutex *sync.Mut
if newMock != nil {
matchedMock = newMock
}
- logger.Debug("Matched In Unsorted PG Matching Stream", zap.String("mock", matchedMock.Name))
+ logger.Info("Matched In Unsorted PG Matching Stream", zap.String("mock", matchedMock.Name))
}
idx = findBinaryStreamMatch(logger, tcsMocks, requestBuffers, sorted)
// check if the validate the query with the matched mock
@@ -273,12 +272,12 @@ func matchingReadablePG(ctx context.Context, logger *zap.Logger, mutex *sync.Mut
if newMock != nil && !isValid {
matchedMock = newMock
}
- logger.Debug("Matched In Binary Matching for Unsorted", zap.String("mock", matchedMock.Name))
+ logger.Info("Matched In Binary Matching for Unsorted", zap.String("mock", matchedMock.Name))
}
}
if matched {
- logger.Debug("Matched mock", zap.String("mock", matchedMock.Name))
+ logger.Info("Matched mock", zap.String("mock", matchedMock.Name))
if matchedMock.TestModeInfo.IsFiltered {
originalMatchedMock := *matchedMock
matchedMock.TestModeInfo.IsFiltered = false
@@ -350,13 +349,13 @@ func findBinaryStreamMatch(logger *zap.Logger, tcsMocks []*models.Mock, requestB
if sorted {
if mxIdx != -1 && mxSim >= 0.78 {
- logger.Debug("Matched with Sorted Stream", zap.Float64("similarity", mxSim))
+ logger.Info("Matched with Sorted Stream", zap.Float64("similarity", mxSim))
} else {
mxIdx = -1
}
} else {
if mxIdx != -1 {
- logger.Debug("Matched with Unsorted Stream", zap.Float64("similarity", mxSim))
+ logger.Info("Matched with Unsorted Stream", zap.Float64("similarity", mxSim))
}
}
return mxIdx
diff --git a/pkg/core/proxy/integrations/postgres/v1/transcoder.go b/pkg/core/proxy/integrations/postgres/v1/transcoder.go
index 9e1ce08c9a..e84158b7d1 100644
--- a/pkg/core/proxy/integrations/postgres/v1/transcoder.go
+++ b/pkg/core/proxy/integrations/postgres/v1/transcoder.go
@@ -111,9 +111,9 @@ func (f *FrontendWrapper) translateToReadableResponse(logger *zap.Logger, msgBod
case 'E':
msg = &f.FrontendWrapper.ErrorResponse
case 'G':
- msg = &f.FrontendWrapper.CopyInResponse
- case 'H':
- msg = &f.FrontendWrapper.CopyOutResponse
+ // msg = &f.FrontendWrapper.CopyInResponse
+ // case 'H':
+ // msg = &f.FrontendWrapper.CopyOutResponse
case 'I':
msg = &f.FrontendWrapper.EmptyQueryResponse
case 'K':
@@ -138,8 +138,6 @@ func (f *FrontendWrapper) translateToReadableResponse(logger *zap.Logger, msgBod
msg = &f.FrontendWrapper.RowDescription
case 'V':
msg = &f.FrontendWrapper.FunctionCallResponse
- case 'W':
- msg = &f.FrontendWrapper.CopyBothResponse
case 'Z':
msg = &f.FrontendWrapper.ReadyForQuery
default:
diff --git a/pkg/core/proxy/integrations/postgres/v1/util.go b/pkg/core/proxy/integrations/postgres/v1/util.go
index e0a385cdc1..64f4a949d6 100755
--- a/pkg/core/proxy/integrations/postgres/v1/util.go
+++ b/pkg/core/proxy/integrations/postgres/v1/util.go
@@ -78,16 +78,16 @@ func postgresDecoderFrontend(response models.Frontend) ([]byte, error) {
Line: response.ErrorResponse.Line,
Routine: response.ErrorResponse.Routine,
}
- case string('G'):
- msg = &pgproto3.CopyInResponse{
- OverallFormat: response.CopyInResponse.OverallFormat,
- ColumnFormatCodes: response.CopyInResponse.ColumnFormatCodes,
- }
- case string('H'):
- msg = &pgproto3.CopyOutResponse{
- OverallFormat: response.CopyOutResponse.OverallFormat,
- ColumnFormatCodes: response.CopyOutResponse.ColumnFormatCodes,
- }
+ // case string('G'):
+ // msg = &pgproto3.CopyInResponse{
+ // OverallFormat: response.CopyInResponse.OverallFormat,
+ // ColumnFormatCodes: response.CopyInResponse.ColumnFormatCodes,
+ // }
+ // case string('H'):
+ // msg = &pgproto3.CopyOutResponse{
+ // OverallFormat: response.CopyOutResponse.OverallFormat,
+ // ColumnFormatCodes: response.CopyOutResponse.ColumnFormatCodes,
+ // }
case string('I'):
msg = &pgproto3.EmptyQueryResponse{}
case string('K'):
@@ -165,11 +165,11 @@ func postgresDecoderFrontend(response models.Frontend) ([]byte, error) {
msg = &pgproto3.FunctionCallResponse{
Result: response.FunctionCallResponse.Result,
}
- case string('W'):
- msg = &pgproto3.CopyBothResponse{
- OverallFormat: response.CopyBothResponse.OverallFormat,
- ColumnFormatCodes: response.CopyBothResponse.ColumnFormatCodes,
- }
+ // case string('W'):
+ // msg = &pgproto3.CopyBothResponse{
+ // // OverallFormat: response.CopyBothResponse.OverallFormat,
+ // ColumnFormatCodes: response.CopyBothResponse.ColumnFormatCodes,
+ // }
case string('Z'):
msg = &pgproto3.ReadyForQuery{
TxStatus: response.ReadyForQuery.TxStatus,
diff --git a/pkg/core/proxy/proxy.go b/pkg/core/proxy/proxy.go
index dbcfed125c..69bfe80fc5 100755
--- a/pkg/core/proxy/proxy.go
+++ b/pkg/core/proxy/proxy.go
@@ -57,6 +57,10 @@ type Proxy struct {
}
func New(logger *zap.Logger, info core.DestInfo, opts *config.Config) *Proxy {
+ if opts.Agent.IsDocker {
+ logger.Info("Running in docker environment proxy port will be set to 36789")
+ opts.ProxyPort = 36789
+ }
return &Proxy{
logger: logger,
Port: opts.ProxyPort, // default: 16789
@@ -89,7 +93,6 @@ func (p *Proxy) StartProxy(ctx context.Context, opts core.ProxyOptions) error {
utils.LogError(p.logger, err, "failed to initialize the integrations")
return err
}
-
// set up the CA for tls connections
err = SetupCA(ctx, p.logger)
if err != nil {
@@ -188,7 +191,7 @@ func (p *Proxy) start(ctx context.Context) error {
return err
}
p.Listener = listener
- p.logger.Debug(fmt.Sprintf("Proxy server is listening on %s", fmt.Sprintf(":%v", listener.Addr())))
+ p.logger.Info(fmt.Sprintf("Proxy server is listening on %s", fmt.Sprintf(":%v", listener.Addr())))
defer func(listener net.Listener) {
err := listener.Close()
@@ -205,7 +208,7 @@ func (p *Proxy) start(ctx context.Context) error {
clientConnCancel()
err := clientConnErrGrp.Wait()
if err != nil {
- p.logger.Debug("failed to handle the client connection", zap.Error(err))
+ p.logger.Info("failed to handle the client connection", zap.Error(err))
}
//closing all the mock channels (if any in record mode)
for _, mc := range p.sessions.GetAllMC() {
@@ -280,8 +283,7 @@ func (p *Proxy) handleConnection(ctx context.Context, srcConn net.Conn) error {
remoteAddr := srcConn.RemoteAddr().(*net.TCPAddr)
sourcePort := remoteAddr.Port
- p.logger.Debug("Inside handleConnection of proxyServer", zap.Any("source port", sourcePort), zap.Any("Time", time.Now().Unix()))
-
+ p.logger.Info("Inside handleConnection of proxyServer", zap.Any("source port", sourcePort), zap.Any("Time", time.Now().Unix()))
destInfo, err := p.DestInfo.Get(ctx, uint16(sourcePort))
if err != nil {
utils.LogError(p.logger, err, "failed to fetch the destination info", zap.Any("Source port", sourcePort))
@@ -570,7 +572,6 @@ func (p *Proxy) Record(_ context.Context, id uint64, mocks chan<- *models.Mock,
MC: mocks,
OutgoingOptions: opts,
})
-
p.MockManagers.Store(id, NewMockManager(NewTreeDb(customComparator), NewTreeDb(customComparator), p.logger))
////set the new proxy ip:port for a new session
@@ -622,7 +623,6 @@ func (p *Proxy) SetMocks(_ context.Context, id uint64, filtered []*models.Mock,
m.(*MockManager).SetFilteredMocks(filtered)
m.(*MockManager).SetUnFilteredMocks(unFiltered)
}
-
return nil
}
diff --git a/pkg/core/record.go b/pkg/core/record.go
deleted file mode 100644
index 471ebf40e5..0000000000
--- a/pkg/core/record.go
+++ /dev/null
@@ -1,24 +0,0 @@
-//go:build linux
-
-package core
-
-import (
- "context"
-
- "go.keploy.io/server/v2/pkg/models"
-)
-
-func (c *Core) GetIncoming(ctx context.Context, id uint64, opts models.IncomingOptions) (<-chan *models.TestCase, error) {
- return c.Hooks.Record(ctx, id, opts)
-}
-
-func (c *Core) GetOutgoing(ctx context.Context, id uint64, opts models.OutgoingOptions) (<-chan *models.Mock, error) {
- m := make(chan *models.Mock, 500)
-
- err := c.Proxy.Record(ctx, id, m, opts)
- if err != nil {
- return nil, err
- }
-
- return m, nil
-}
diff --git a/pkg/core/replay.go b/pkg/core/replay.go
deleted file mode 100644
index 4bc6cfdd6e..0000000000
--- a/pkg/core/replay.go
+++ /dev/null
@@ -1,19 +0,0 @@
-//go:build linux
-
-package core
-
-import (
- "context"
-
- "go.keploy.io/server/v2/pkg/models"
-)
-
-func (c *Core) MockOutgoing(ctx context.Context, id uint64, opts models.OutgoingOptions) error {
-
- err := c.Proxy.Mock(ctx, id, opts)
- if err != nil {
- return err
- }
-
- return nil
-}
diff --git a/pkg/core/service.go b/pkg/core/service.go
index 27c43d835f..e2b4bd5af5 100644
--- a/pkg/core/service.go
+++ b/pkg/core/service.go
@@ -15,11 +15,13 @@ import (
)
type Hooks interface {
- AppInfo
DestInfo
OutgoingInfo
Load(ctx context.Context, id uint64, cfg HookCfg) error
Record(ctx context.Context, id uint64, opts models.IncomingOptions) (<-chan *models.TestCase, error)
+ // send KeployClient Pid
+ SendKeployClientInfo(ctx context.Context, clientID uint64, clientInfo structs.ClientInfo) error
+ SendClientProxyInfo(ctx context.Context, clientID uint64, proxyInfo structs.ProxyInfo) error
}
type HookCfg struct {
@@ -33,7 +35,7 @@ type HookCfg struct {
type App interface {
Setup(ctx context.Context, opts app.Options) error
- Run(ctx context.Context, inodeChan chan uint64, opts app.Options) error
+ Run(ctx context.Context, opts app.Options) error
Kind(ctx context.Context) utils.CmdType
KeployIPv4Addr() string
}
@@ -59,10 +61,6 @@ type DestInfo interface {
Delete(ctx context.Context, srcPort uint16) error
}
-type AppInfo interface {
- SendDockerAppInfo(id uint64, dockerAppInfo structs.DockerAppInfo) error
-}
-
// For keploy test bench
type Tester interface {
diff --git a/pkg/models/agent.go b/pkg/models/agent.go
new file mode 100644
index 0000000000..1e64c2499b
--- /dev/null
+++ b/pkg/models/agent.go
@@ -0,0 +1,32 @@
+package models
+
+type OutgoingReq struct {
+ OutgoingOptions OutgoingOptions `json:"outgoingOptions"`
+ ClientID uint64 `json:"clientId"`
+}
+
+type IncomingReq struct {
+ IncomingOptions IncomingOptions `json:"incomingOptions"`
+ ClientID uint64 `json:"clientId"`
+}
+
+type RegisterReq struct {
+ SetupOptions SetupOptions `json:"setupOptions"`
+}
+
+type AgentResp struct {
+ ClientID int64 `json:"clientId"` // uuid of the app
+ Error error `json:"error"`
+ IsSuccess bool `json:"isSuccess"`
+}
+
+type RunReq struct {
+ RunOptions RunOptions `json:"runOptions"`
+ ClientId uint64 `json:"clientId"`
+}
+
+type SetMocksReq struct {
+ Filtered []*Mock `json:"filtered"`
+ UnFiltered []*Mock `json:"unFiltered"`
+ AppId uint64 `json:"appId"`
+}
diff --git a/pkg/models/instrument.go b/pkg/models/instrument.go
index 8f38f165c5..9d8a30d4e0 100644
--- a/pkg/models/instrument.go
+++ b/pkg/models/instrument.go
@@ -10,6 +10,7 @@ type HookOptions struct {
Rules []config.BypassRule
Mode Mode
EnableTesting bool
+ IsDocker bool
}
type OutgoingOptions struct {
@@ -26,9 +27,17 @@ type IncomingOptions struct {
}
type SetupOptions struct {
+ ClientID uint64
Container string
DockerNetwork string
DockerDelay uint64
+ ClientNsPid uint32
+ ClientInode uint64
+ AppInode uint64
+ Cmd string
+ IsDocker bool
+ CommandType string
+ Mode Mode
}
type RunOptions struct {
diff --git a/pkg/models/mysql/comm.go b/pkg/models/mysql/comm.go
index c0ed297ca8..2dfef943f4 100644
--- a/pkg/models/mysql/comm.go
+++ b/pkg/models/mysql/comm.go
@@ -1,4 +1,4 @@
-// Package mysql in models provides realted structs for mysql protocol
+// Package mysql in models provides related structs for mysql protocol
package mysql
// This file contains struct for command phase packets
@@ -13,157 +13,157 @@ package mysql
// COM_QUERY packet (currently does not support if CLIENT_QUERY_ATTRIBUTES is set)
type QueryPacket struct {
- Command byte `yaml:"command"`
- Query string `yaml:"query"`
+ Command byte `yaml:"command" json:"command"`
+ Query string `yaml:"query" json:"query"`
}
// LocalInFileRequestPacket is used to send local file request to server, currently not supported
type LocalInFileRequestPacket struct {
- PacketType byte `yaml:"command"`
- Filename string
+ PacketType byte `yaml:"command" json:"command"`
+ Filename string `yaml:"filename" json:"filename"`
}
// TextResultSet is used as a response packet for COM_QUERY
type TextResultSet struct {
- ColumnCount uint64 `yaml:"columnCount"`
- Columns []*ColumnDefinition41 `yaml:"columns"`
- EOFAfterColumns []byte `yaml:"eofAfterColumns"`
- Rows []*TextRow `yaml:"rows"`
- FinalResponse *GenericResponse `yaml:"FinalResponse"`
+ ColumnCount uint64 `yaml:"columnCount" json:"columnCount"`
+ Columns []*ColumnDefinition41 `yaml:"columns" json:"columns"`
+ EOFAfterColumns []byte `yaml:"eofAfterColumns" json:"eofAfterColumns"`
+ Rows []*TextRow `yaml:"rows" json:"rows"`
+ FinalResponse *GenericResponse `yaml:"FinalResponse" json:"FinalResponse"`
}
// BinaryProtocolResultSet is used as a response packet for COM_STMT_EXECUTE
type BinaryProtocolResultSet struct {
- ColumnCount uint64 `yaml:"columnCount"`
- Columns []*ColumnDefinition41 `yaml:"columns"`
- EOFAfterColumns []byte `yaml:"eofAfterColumns"`
- Rows []*BinaryRow `yaml:"rows"`
- FinalResponse *GenericResponse `yaml:"FinalResponse"`
+ ColumnCount uint64 `yaml:"columnCount" json:"columnCount"`
+ Columns []*ColumnDefinition41 `yaml:"columns" json:"columns"`
+ EOFAfterColumns []byte `yaml:"eofAfterColumns" json:"eofAfterColumns"`
+ Rows []*BinaryRow `yaml:"rows" json:"rows"`
+ FinalResponse *GenericResponse `yaml:"FinalResponse" json:"FinalResponse"`
}
type GenericResponse struct {
- Data []byte `yaml:"data"`
- Type string `yaml:"type"`
+ Data []byte `yaml:"data" json:"data"`
+ Type string `yaml:"type" json:"type"`
}
// Columns
type ColumnCount struct {
- // Header Header `yaml:"header"`
- Count uint64 `yaml:"count"`
+ // Header Header `yaml:"header" json:"header"`
+ Count uint64 `yaml:"count" json:"count"`
}
type ColumnDefinition41 struct {
- Header Header `yaml:"header"`
- Catalog string `yaml:"catalog"`
- Schema string `yaml:"schema"`
- Table string `yaml:"table"`
- OrgTable string `yaml:"org_table"`
- Name string `yaml:"name"`
- OrgName string `yaml:"org_name"`
- FixedLength byte `yaml:"fixed_length"`
- CharacterSet uint16 `yaml:"character_set"`
- ColumnLength uint32 `yaml:"column_length"`
- Type byte `yaml:"type"`
- Flags uint16 `yaml:"flags"`
- Decimals byte `yaml:"decimals"`
- Filler []byte `yaml:"filler"`
- DefaultValue string `yaml:"defaultValue"`
-}
-
-//Rows
+ Header Header `yaml:"header" json:"header"`
+ Catalog string `yaml:"catalog" json:"catalog"`
+ Schema string `yaml:"schema" json:"schema"`
+ Table string `yaml:"table" json:"table"`
+ OrgTable string `yaml:"org_table" json:"org_table"`
+ Name string `yaml:"name" json:"name"`
+ OrgName string `yaml:"org_name" json:"org_name"`
+ FixedLength byte `yaml:"fixed_length" json:"fixed_length"`
+ CharacterSet uint16 `yaml:"character_set" json:"character_set"`
+ ColumnLength uint32 `yaml:"column_length" json:"column_length"`
+ Type byte `yaml:"type" json:"type"`
+ Flags uint16 `yaml:"flags" json:"flags"`
+ Decimals byte `yaml:"decimals" json:"decimals"`
+ Filler []byte `yaml:"filler" json:"filler"`
+ DefaultValue string `yaml:"defaultValue" json:"defaultValue"`
+}
+
+// Rows
type TextRow struct {
- Header Header `yaml:"header"`
- Values []ColumnEntry `yaml:"values"`
+ Header Header `yaml:"header" json:"header"`
+ Values []ColumnEntry `yaml:"values" json:"values"`
}
type BinaryRow struct {
- Header Header `yaml:"header"`
- Values []ColumnEntry `yaml:"values"`
- OkAfterRow bool `yaml:"okAfterRow"`
- RowNullBuffer []byte `yaml:"rowNullBuffer"`
+ Header Header `yaml:"header" json:"header"`
+ Values []ColumnEntry `yaml:"values" json:"values"`
+ OkAfterRow bool `yaml:"okAfterRow" json:"okAfterRow"`
+ RowNullBuffer []byte `yaml:"rowNullBuffer" json:"rowNullBuffer"`
}
type ColumnEntry struct {
- Type FieldType `yaml:"type"`
- Name string `yaml:"name"`
- Value interface{} `yaml:"value"`
- Unsigned bool `yaml:"unsigned"`
+ Type FieldType `yaml:"type" json:"type"`
+ Name string `yaml:"name" json:"name"`
+ Value interface{} `yaml:"value" json:"value"`
+ Unsigned bool `yaml:"unsigned" json:"unsigned"`
}
// COM_STMT_PREPARE packet
type StmtPreparePacket struct {
- Command byte `yaml:"command"`
- Query string `yaml:"query"`
+ Command byte `yaml:"command" json:"command"`
+ Query string `yaml:"query" json:"query"`
}
// COM_STMT_PREPARE_OK packet
type StmtPrepareOkPacket struct {
- Status byte `yaml:"status"`
- StatementID uint32 `yaml:"statement_id"`
- NumColumns uint16 `yaml:"num_columns"`
- NumParams uint16 `yaml:"num_params"`
- Filler byte `yaml:"filler"`
- WarningCount uint16 `yaml:"warning_count"`
+ Status byte `yaml:"status" json:"status"`
+ StatementID uint32 `yaml:"statement_id" json:"statement_id"`
+ NumColumns uint16 `yaml:"num_columns" json:"num_columns"`
+ NumParams uint16 `yaml:"num_params" json:"num_params"`
+ Filler byte `yaml:"filler" json:"filler"`
+ WarningCount uint16 `yaml:"warning_count" json:"warning_count"`
- ParamDefs []*ColumnDefinition41 `yaml:"param_definitions"`
- EOFAfterParamDefs []byte `yaml:"eofAfterParamDefs"`
- ColumnDefs []*ColumnDefinition41 `yaml:"column_definitions"`
- EOFAfterColumnDefs []byte `yaml:"eofAfterColumnDefs"`
+ ParamDefs []*ColumnDefinition41 `yaml:"param_definitions" json:"param_definitions"`
+ EOFAfterParamDefs []byte `yaml:"eofAfterParamDefs" json:"eofAfterParamDefs"`
+ ColumnDefs []*ColumnDefinition41 `yaml:"column_definitions" json:"column_definitions"`
+ EOFAfterColumnDefs []byte `yaml:"eofAfterColumnDefs" json:"eofAfterColumnDefs"`
}
// COM_STMT_EXECUTE packet
type StmtExecutePacket struct {
- Status byte `yaml:"status"`
- StatementID uint32 `yaml:"statement_id"`
- Flags byte `yaml:"flags"`
- IterationCount uint32 `yaml:"iteration_count"`
- ParameterCount int `yaml:"parameter_count"`
- NullBitmap []byte `yaml:"null_bitmap"`
- NewParamsBindFlag byte `yaml:"new_params_bind_flag"`
- Parameters []Parameter `yaml:"parameters"`
+ Status byte `yaml:"status" json:"status"`
+ StatementID uint32 `yaml:"statement_id" json:"statement_id"`
+ Flags byte `yaml:"flags" json:"flags"`
+ IterationCount uint32 `yaml:"iteration_count" json:"iteration_count"`
+ ParameterCount int `yaml:"parameter_count" json:"parameter_count"`
+ NullBitmap []byte `yaml:"null_bitmap" json:"null_bitmap"`
+ NewParamsBindFlag byte `yaml:"new_params_bind_flag" json:"new_params_bind_flag"`
+ Parameters []Parameter `yaml:"parameters" json:"parameters"`
}
type Parameter struct {
- Type uint16 `yaml:"type"`
- Unsigned bool `yaml:"unsigned"`
- Name string `yaml:"name,omitempty"`
- Value []byte `yaml:"value"`
+ Type uint16 `yaml:"type" json:"type"`
+ Unsigned bool `yaml:"unsigned" json:"unsigned"`
+ Name string `yaml:"name,omitempty" json:"name,omitempty"`
+ Value []byte `yaml:"value" json:"value"`
}
// COM_STMT_FETCH packet is not currently supported because its response involves multi-resultset
type StmtFetchPacket struct {
- Status byte `yaml:"status"`
- StatementID uint32 `yaml:"statement_id"`
- NumRows uint32 `yaml:"num_rows"`
+ Status byte `yaml:"status" json:"status"`
+ StatementID uint32 `yaml:"statement_id" json:"statement_id"`
+ NumRows uint32 `yaml:"num_rows" json:"num_rows"`
}
// COM_STMT_CLOSE packet
type StmtClosePacket struct {
- Status byte `yaml:"status"`
- StatementID uint32 `yaml:"statement_id"`
+ Status byte `yaml:"status" json:"status"`
+ StatementID uint32 `yaml:"statement_id" json:"statement_id"`
}
// COM_STMT_RESET packet
type StmtResetPacket struct {
- Status byte `yaml:"status"`
- StatementID uint32 `yaml:"statement_id"`
+ Status byte `yaml:"status" json:"status"`
+ StatementID uint32 `yaml:"statement_id" json:"statement_id"`
}
// COM_STMT_SEND_LONG_DATA packet
type StmtSendLongDataPacket struct {
- Status byte `yaml:"status"`
- StatementID uint32 `yaml:"statement_id"`
- ParameterID uint16 `yaml:"parameter_id"`
- Data []byte `yaml:"data"`
+ Status byte `yaml:"status" json:"status"`
+ StatementID uint32 `yaml:"statement_id" json:"statement_id"`
+ ParameterID uint16 `yaml:"parameter_id" json:"parameter_id"`
+ Data []byte `yaml:"data" json:"data"`
}
// Utility commands
@@ -171,51 +171,51 @@ type StmtSendLongDataPacket struct {
// COM_QUIT packet
type QuitPacket struct {
- Command byte `yaml:"command"`
+ Command byte `yaml:"command" json:"command"`
}
// COM_INIT_DB packet
type InitDBPacket struct {
- Command byte `yaml:"command"`
- Schema string `yaml:"schema"`
+ Command byte `yaml:"command" json:"command"`
+ Schema string `yaml:"schema" json:"schema"`
}
// COM_STATISTICS packet
type StatisticsPacket struct {
- Command byte `yaml:"command"`
+ Command byte `yaml:"command" json:"command"`
}
// COM_DEBUG packet
type DebugPacket struct {
- Command byte `yaml:"command"`
+ Command byte `yaml:"command" json:"command"`
}
// COM_PING packet
type PingPacket struct {
- Command byte `yaml:"command"`
+ Command byte `yaml:"command" json:"command"`
}
// COM_RESET_CONNECTION packet
type ResetConnectionPacket struct {
- Command byte `yaml:"command"`
+ Command byte `yaml:"command" json:"command"`
}
// COM_SET_OPTION packet
type SetOptionPacket struct {
- Status byte `yaml:"status"`
- Option uint16 `yaml:"option"`
+ Status byte `yaml:"status" json:"status"`
+ Option uint16 `yaml:"option" json:"option"`
}
// COM_CHANGE_USER packet (Not completed/supported as of now)
//refer: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_change_user.html
type ChangeUserPacket struct {
- Command byte `yaml:"command"`
+ Command byte `yaml:"command" json:"command"`
// rest of the fields are not present as the packet is not supported
}
diff --git a/pkg/models/mysql/conn.go b/pkg/models/mysql/conn.go
index b7726e4b09..71f5e6957d 100644
--- a/pkg/models/mysql/conn.go
+++ b/pkg/models/mysql/conn.go
@@ -7,55 +7,55 @@ package mysql
// HandshakeV10Packet represents the initial handshake packet sent by the server to the client
type HandshakeV10Packet struct {
- ProtocolVersion uint8 `yaml:"protocol_version"`
- ServerVersion string `yaml:"server_version"`
- ConnectionID uint32 `yaml:"connection_id"`
- AuthPluginData []byte `yaml:"auth_plugin_data,omitempty,flow"`
- Filler byte `yaml:"filler"`
- CapabilityFlags uint32 `yaml:"capability_flags"`
- CharacterSet uint8 `yaml:"character_set"`
- StatusFlags uint16 `yaml:"status_flags"`
- AuthPluginName string `yaml:"auth_plugin_name"`
+ ProtocolVersion uint8 `yaml:"protocol_version" json:"protocol_version"`
+ ServerVersion string `yaml:"server_version" json:"server_version"`
+ ConnectionID uint32 `yaml:"connection_id" json:"connection_id"`
+ AuthPluginData []byte `yaml:"auth_plugin_data,omitempty,flow" json:"auth_plugin_data,omitempty,flow"`
+ Filler byte `yaml:"filler" json:"filler"`
+ CapabilityFlags uint32 `yaml:"capability_flags" json:"capability_flags"`
+ CharacterSet uint8 `yaml:"character_set" json:"character_set"`
+ StatusFlags uint16 `yaml:"status_flags" json:"status_flags"`
+ AuthPluginName string `yaml:"auth_plugin_name" json:"auth_plugin_name"`
}
// HandshakeResponse41Packet represents the response packet sent by the client to the server after receiving the HandshakeV10Packet
type HandshakeResponse41Packet struct {
- CapabilityFlags uint32 `yaml:"capability_flags"`
- MaxPacketSize uint32 `yaml:"max_packet_size"`
- CharacterSet uint8 `yaml:"character_set"`
- Filler [23]byte `yaml:"filler,omitempty,flow"`
- Username string `yaml:"username"`
- AuthResponse []byte `yaml:"auth_response,omitempty,flow"`
- Database string `yaml:"database"`
- AuthPluginName string `yaml:"auth_plugin_name"`
- ConnectionAttributes map[string]string `yaml:"connection_attributes,omitempty"`
- ZstdCompressionLevel byte `yaml:"zstdcompressionlevel"`
+ CapabilityFlags uint32 `yaml:"capability_flags" json:"capability_flags"`
+ MaxPacketSize uint32 `yaml:"max_packet_size" json:"max_packet_size"`
+ CharacterSet uint8 `yaml:"character_set" json:"character_set"`
+ Filler [23]byte `yaml:"filler,omitempty,flow" json:"filler,omitempty,flow"`
+ Username string `yaml:"username" json:"username"`
+ AuthResponse []byte `yaml:"auth_response,omitempty,flow" json:"auth_response,omitempty"`
+ Database string `yaml:"database" json:"database"`
+ AuthPluginName string `yaml:"auth_plugin_name" json:"auth_plugin_name"`
+ ConnectionAttributes map[string]string `yaml:"connection_attributes,omitempty" json:"connection_attributes,omitempty"`
+ ZstdCompressionLevel byte `yaml:"zstdcompressionlevel" json:"zstdcompressionlevel"`
}
// Authentication Packets
// AuthSwitchRequestPacket represents the packet sent by the server to the client to switch to a different authentication method
type AuthSwitchRequestPacket struct {
- StatusTag byte `yaml:"status_tag"`
- PluginName string `yaml:"plugin_name"`
- PluginData string `yaml:"plugin_data"`
+ StatusTag byte `yaml:"status_tag" json:"status_tag"`
+ PluginName string `yaml:"plugin_name" json:"plugin_name"`
+ PluginData string `yaml:"plugin_data" json:"plugin_data"`
}
// AuthSwitchResponsePacket represents the packet sent by the client to the server in response to an AuthSwitchRequestPacket.
// Note: If the server sends an AuthMoreDataPacket, the client will continue sending AuthSwitchResponsePackets until the server sends an OK packet or an ERR packet.
type AuthSwitchResponsePacket struct {
- Data string `yaml:"data"`
+ Data string `yaml:"data" json:"data"`
}
// AuthMoreDataPacket represents the packet sent by the server to the client to request additional data for authentication
type AuthMoreDataPacket struct {
- StatusTag byte `yaml:"status_tag"`
- Data string `yaml:"data"`
+ StatusTag byte `yaml:"status_tag" json:"status_tag"`
+ Data string `yaml:"data" json:"data"`
}
// AuthNextFactorPacket represents the packet sent by the server to the client to request the next factor for multi-factor authentication
type AuthNextFactorPacket struct {
- PacketType byte `yaml:"packet_type"`
- PluginName string `yaml:"plugin_name"`
- PluginData string `yaml:"plugin_data"`
+ PacketType byte `yaml:"packet_type" json:"packet_type"`
+ PluginName string `yaml:"plugin_name" json:"plugin_name"`
+ PluginData string `yaml:"plugin_data" json:"plugin_data"`
}
diff --git a/pkg/models/mysql/generic.go b/pkg/models/mysql/generic.go
index 07be522951..358dea3b19 100644
--- a/pkg/models/mysql/generic.go
+++ b/pkg/models/mysql/generic.go
@@ -1,7 +1,7 @@
package mysql
// This file contains structs for mysql generic response packets
-//refer: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_response_packets.html
+// refer: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_response_packets.html
// OKPacket represents the OK packet sent by the server to the client, it represents a successful completion of a command
type OKPacket struct {
@@ -15,16 +15,16 @@ type OKPacket struct {
// ERRPacket represents the ERR packet sent by the server to the client, it represents an error occurred during the execution of a command
type ERRPacket struct {
- Header byte `yaml:"header"`
- ErrorCode uint16 `yaml:"error_code"`
- SQLStateMarker string `yaml:"sql_state_marker"`
- SQLState string `yaml:"sql_state"`
- ErrorMessage string `yaml:"error_message"`
+ Header byte `json:"header" yaml:"header"`
+ ErrorCode uint16 `json:"error_code" yaml:"error_code"`
+ SQLStateMarker string `json:"sql_state_marker" yaml:"sql_state_marker"`
+ SQLState string `json:"sql_state" yaml:"sql_state"`
+ ErrorMessage string `json:"error_message" yaml:"error_message"`
}
// EOFPacket represents the EOF packet sent by the server to the client, it represents the end of a query execution result
type EOFPacket struct {
- Header byte `yaml:"header"`
- Warnings uint16 `yaml:"warnings"`
- StatusFlags uint16 `yaml:"status_flags"`
+ Header byte `json:"header" yaml:"header"`
+ Warnings uint16 `json:"warnings" yaml:"warnings"`
+ StatusFlags uint16 `json:"status_flags" yaml:"status_flags"`
}
diff --git a/pkg/models/mysql/mysql.go b/pkg/models/mysql/mysql.go
index c1f6389a7e..af810d83a6 100644
--- a/pkg/models/mysql/mysql.go
+++ b/pkg/models/mysql/mysql.go
@@ -1,6 +1,9 @@
package mysql
import (
+ "encoding/json"
+ "errors"
+ "fmt"
"time"
"gopkg.in/yaml.v3"
@@ -59,3 +62,304 @@ type Header struct {
PayloadLength uint32 `json:"payload_length" yaml:"payload_length"`
SequenceID uint8 `json:"sequence_id" yaml:"sequence_id"`
}
+
+// custom marshal and unmarshal methods for Request and Response structs
+
+// MarshalJSON implements json.Marshaler for Request because of interface type of field 'Message'
+func (r *Request) MarshalJSON() ([]byte, error) {
+ // create an alias struct to avoid infinite recursion
+ type RequestAlias struct {
+ Header *PacketInfo `json:"header"`
+ Message json.RawMessage `json:"message"`
+ Meta map[string]string `json:"meta,omitempty"`
+ }
+
+ aux := RequestAlias{
+ Header: r.Header,
+ Message: json.RawMessage(nil),
+ Meta: r.Meta,
+ }
+
+ if r.Message != nil {
+ // Marshal the message interface{} into JSON
+ msgJSON, err := json.Marshal(r.Message)
+ if err != nil {
+ return nil, err
+ }
+ fmt.Println("msgJSON::", string(msgJSON))
+ aux.Message = msgJSON
+ }
+
+ // Marshal the alias struct into JSON
+ return json.Marshal(aux)
+}
+
+// UnmarshalJSON implements json.Unmarshaler for Request because of interface type of field 'Message'
+func (r *Request) UnmarshalJSON(data []byte) error {
+ // Alias struct to prevent recursion during unmarshalling
+ type RequestAlias struct {
+ Header *PacketInfo `json:"header"`
+ Message json.RawMessage `json:"message"`
+ Meta map[string]string `json:"meta,omitempty"`
+ }
+ var aux RequestAlias
+
+ // Unmarshal the data into the alias
+ if err := json.Unmarshal(data, &aux); err != nil {
+ return err
+ }
+
+ // Assign the unmarshalled data to the original struct
+ r.Header = aux.Header
+ r.Meta = aux.Meta
+
+ // Unmarshal the message field based on the type in the header
+ switch r.Header.Type {
+ case HandshakeResponse41:
+ var msg HandshakeResponse41Packet
+ if err := json.Unmarshal(aux.Message, &msg); err != nil {
+ return err
+ }
+ r.Message = &msg
+
+ case CachingSha2PasswordToString(RequestPublicKey):
+ var msg string
+ if err := json.Unmarshal(aux.Message, &msg); err != nil {
+ return err
+ }
+ r.Message = msg
+
+ case "encrypted_password":
+ var msg string
+ if err := json.Unmarshal(aux.Message, &msg); err != nil {
+ return err
+ }
+ r.Message = msg
+
+ case CommandStatusToString(COM_QUIT):
+ var msg QuitPacket
+ if err := json.Unmarshal(aux.Message, &msg); err != nil {
+ return err
+ }
+ r.Message = &msg
+
+ case CommandStatusToString(COM_INIT_DB):
+ var msg InitDBPacket
+ if err := json.Unmarshal(aux.Message, &msg); err != nil {
+ return err
+ }
+ r.Message = &msg
+
+ case CommandStatusToString(COM_STATISTICS):
+ var msg StatisticsPacket
+ if err := json.Unmarshal(aux.Message, &msg); err != nil {
+ return err
+ }
+ r.Message = &msg
+
+ case CommandStatusToString(COM_DEBUG):
+ var msg DebugPacket
+ if err := json.Unmarshal(aux.Message, &msg); err != nil {
+ return err
+ }
+ r.Message = &msg
+
+ case CommandStatusToString(COM_PING):
+ var msg PingPacket
+ if err := json.Unmarshal(aux.Message, &msg); err != nil {
+ return err
+ }
+ r.Message = &msg
+
+ case CommandStatusToString(COM_CHANGE_USER):
+ var msg ChangeUserPacket
+ if err := json.Unmarshal(aux.Message, &msg); err != nil {
+ return err
+ }
+ r.Message = &msg
+
+ case CommandStatusToString(COM_RESET_CONNECTION):
+ var msg ResetConnectionPacket
+ if err := json.Unmarshal(aux.Message, &msg); err != nil {
+ return err
+ }
+ r.Message = &msg
+
+ case CommandStatusToString(COM_QUERY):
+ var msg QueryPacket
+ if err := json.Unmarshal(aux.Message, &msg); err != nil {
+ return err
+ }
+ r.Message = &msg
+
+ case CommandStatusToString(COM_STMT_PREPARE):
+ var msg StmtPreparePacket
+ if err := json.Unmarshal(aux.Message, &msg); err != nil {
+ return err
+ }
+ r.Message = &msg
+
+ case CommandStatusToString(COM_STMT_EXECUTE):
+ var msg StmtExecutePacket
+ if err := json.Unmarshal(aux.Message, &msg); err != nil {
+ return err
+ }
+ r.Message = &msg
+
+ case CommandStatusToString(COM_STMT_CLOSE):
+ var msg StmtClosePacket
+ if err := json.Unmarshal(aux.Message, &msg); err != nil {
+ return err
+ }
+ r.Message = &msg
+
+ case CommandStatusToString(COM_STMT_RESET):
+ var msg StmtResetPacket
+ if err := json.Unmarshal(aux.Message, &msg); err != nil {
+ return err
+ }
+ r.Message = &msg
+
+ case CommandStatusToString(COM_STMT_SEND_LONG_DATA):
+ var msg StmtSendLongDataPacket
+ if err := json.Unmarshal(aux.Message, &msg); err != nil {
+ return err
+ }
+ r.Message = &msg
+
+ default:
+ return errors.New("failed to unmarshal unknown request packet type")
+ }
+
+ fmt.Println("r.Message::", r.Message)
+
+ return nil
+}
+
+// MarshalJSON implements json.Marshaler for Response because of interface type of field 'Message'
+func (r *Response) MarshalJSON() ([]byte, error) {
+ // Alias to avoid recursion
+ type ResponseAlias struct {
+ PacketBundle `json:"packet_bundle"`
+ Payload string `json:"payload,omitempty"`
+ Message json.RawMessage `json:"message"`
+ }
+
+ aux := ResponseAlias{
+ PacketBundle: r.PacketBundle,
+ Payload: r.Payload,
+ }
+
+ if r.Message != nil {
+ // Marshal the message interface{} into JSON
+ msgJSON, err := json.Marshal(r.Message)
+ if err != nil {
+ return nil, err
+ }
+ aux.Message = msgJSON
+ }
+
+ return json.Marshal(aux)
+}
+
+// UnmarshalJSON implements json.Unmarshaler for Response because of interface type of field 'Message'
+func (r *Response) UnmarshalJSON(data []byte) error {
+ // Alias struct to prevent recursion
+ type ResponseAlias struct {
+ PacketBundle `json:"packet_bundle"`
+ Payload string `json:"payload,omitempty"`
+ Message json.RawMessage `json:"message"`
+ }
+ var aux ResponseAlias
+
+ // Unmarshal the data into the alias
+ if err := json.Unmarshal(data, &aux); err != nil {
+ return err
+ }
+
+ // Assign the unmarshalled data to the original struct
+ r.PacketBundle = aux.PacketBundle
+ r.Payload = aux.Payload
+
+ // Unmarshal the message field based on the type in the header
+ switch r.PacketBundle.Header.Type {
+ // Generic response
+ case StatusToString(EOF):
+ var msg EOFPacket
+ if err := json.Unmarshal(aux.Message, &msg); err != nil {
+ return err
+ }
+ r.Message = &msg
+
+ case StatusToString(ERR):
+ var msg ERRPacket
+ if err := json.Unmarshal(aux.Message, &msg); err != nil {
+ return err
+ }
+ r.Message = &msg
+
+ case StatusToString(OK):
+ var msg OKPacket
+ if err := json.Unmarshal(aux.Message, &msg); err != nil {
+ return err
+ }
+ r.Message = &msg
+
+
+ // Connection phase
+ case AuthStatusToString(HandshakeV10):
+ var msg HandshakeV10Packet
+ if err := json.Unmarshal(aux.Message, &msg); err != nil {
+ return err
+ }
+ r.Message = &msg
+
+ case AuthStatusToString(AuthSwitchRequest):
+ var msg AuthSwitchRequestPacket
+ if err := json.Unmarshal(aux.Message, &msg); err != nil {
+ return err
+ }
+ r.Message = &msg
+
+ case AuthStatusToString(AuthMoreData):
+ var msg AuthMoreDataPacket
+ if err := json.Unmarshal(aux.Message, &msg); err != nil {
+ return err
+ }
+ r.Message = &msg
+
+ case AuthStatusToString(AuthNextFactor): // not supported yet
+ var msg AuthNextFactorPacket
+ if err := json.Unmarshal(aux.Message, &msg); err != nil {
+ return err
+ }
+ r.Message = &msg
+
+ // Command phase
+ case COM_STMT_PREPARE_OK:
+ var msg StmtPrepareOkPacket
+ if err := json.Unmarshal(aux.Message, &msg); err != nil {
+ return err
+ }
+ r.Message = &msg
+
+ case string(Text):
+ var msg TextResultSet
+ if err := json.Unmarshal(aux.Message, &msg); err != nil {
+ return err
+ }
+ r.Message = &msg
+
+ case string(Binary):
+ var msg BinaryProtocolResultSet
+ if err := json.Unmarshal(aux.Message, &msg); err != nil {
+ return err
+ }
+ r.Message = &msg
+
+ default:
+ return errors.New("failed to unmarshal unknown response packet type")
+ }
+
+ return nil
+}
diff --git a/pkg/models/postgres.go b/pkg/models/postgres.go
index bb8306b076..faa4da6fdc 100755
--- a/pkg/models/postgres.go
+++ b/pkg/models/postgres.go
@@ -34,12 +34,12 @@ type Backend struct {
CopyData pgproto3.CopyData `json:"copy_data,omitempty" yaml:"copy_data,omitempty"`
CopyDone pgproto3.CopyDone `json:"copy_done,omitempty" yaml:"copy_done,omitempty"`
Describe pgproto3.Describe `json:"describe,omitempty" yaml:"describe,omitempty"`
- Execute pgproto3.Execute `yaml:"-"`
+ Execute pgproto3.Execute `json:"-" yaml:"-"`
Executes []pgproto3.Execute `json:"execute,omitempty" yaml:"execute,omitempty"`
Flush pgproto3.Flush `json:"flush,omitempty" yaml:"flush,omitempty"`
FunctionCall pgproto3.FunctionCall `json:"function_call,omitempty" yaml:"function_call,omitempty"`
GssEncRequest pgproto3.GSSEncRequest `json:"gss_enc_request,omitempty" yaml:"gss_enc_request,omitempty"`
- Parse pgproto3.Parse `yaml:"-"`
+ Parse pgproto3.Parse `json:"-" yaml:"-"`
Parses []pgproto3.Parse `json:"parse,omitempty" yaml:"parse,omitempty"`
Query pgproto3.Query `json:"query,omitempty" yaml:"query,omitempty"`
SSlRequest pgproto3.SSLRequest `json:"ssl_request,omitempty" yaml:"ssl_request,omitempty"`
@@ -70,45 +70,33 @@ type Frontend struct {
AuthenticationSASLContinue pgproto3.AuthenticationSASLContinue `json:"authentication_sasl_continue,omitempty" yaml:"authentication_sasl_continue,omitempty,flow"`
AuthenticationSASLFinal pgproto3.AuthenticationSASLFinal `json:"authentication_sasl_final,omitempty" yaml:"authentication_sasl_final,omitempty,flow"`
BackendKeyData pgproto3.BackendKeyData `json:"backend_key_data,omitempty" yaml:"backend_key_data,omitempty"`
- BindComplete pgproto3.BindComplete `yaml:"-"`
+ BindComplete pgproto3.BindComplete `json:"-" yaml:"-"`
BindCompletes []pgproto3.BindComplete `json:"bind_complete,omitempty" yaml:"bind_complete,omitempty"`
CloseComplete pgproto3.CloseComplete `json:"close_complete,omitempty" yaml:"close_complete,omitempty"`
- CommandComplete pgproto3.CommandComplete `yaml:"-"`
+ CommandComplete pgproto3.CommandComplete `json:"-" yaml:"-"`
CommandCompletes []pgproto3.CommandComplete `json:"command_complete,omitempty" yaml:"command_complete,omitempty"`
- CopyBothResponse pgproto3.CopyBothResponse `json:"copy_both_response,omitempty" yaml:"copy_both_response,omitempty"`
- CopyData pgproto3.CopyData `json:"copy_data,omitempty" yaml:"copy_data,omitempty"`
- CopyInResponse pgproto3.CopyInResponse `json:"copy_in_response,omitempty" yaml:"copy_in_response,omitempty"`
- CopyOutResponse pgproto3.CopyOutResponse `json:"copy_out_response,omitempty" yaml:"copy_out_response,omitempty"`
- CopyDone pgproto3.CopyDone `json:"copy_done,omitempty" yaml:"copy_done,omitempty"`
- DataRow pgproto3.DataRow `yaml:"-"`
- DataRows []pgproto3.DataRow `json:"data_row,omitempty" yaml:"data_row,omitempty,flow"`
- EmptyQueryResponse pgproto3.EmptyQueryResponse `json:"empty_query_response,omitempty" yaml:"empty_query_response,omitempty"`
- ErrorResponse pgproto3.ErrorResponse `json:"error_response,omitempty" yaml:"error_response,omitempty"`
- FunctionCallResponse pgproto3.FunctionCallResponse `json:"function_call_response,omitempty" yaml:"function_call_response,omitempty"`
- NoData pgproto3.NoData `json:"no_data,omitempty" yaml:"no_data,omitempty"`
- NoticeResponse pgproto3.NoticeResponse `json:"notice_response,omitempty" yaml:"notice_response,omitempty"`
- NotificationResponse pgproto3.NotificationResponse `json:"notification_response,omitempty" yaml:"notification_response,omitempty"`
- ParameterDescription pgproto3.ParameterDescription `json:"parameter_description,omitempty" yaml:"parameter_description,omitempty"`
- ParameterStatus pgproto3.ParameterStatus `yaml:"-"`
- ParameterStatusCombined []pgproto3.ParameterStatus `json:"parameter_status,omitempty" yaml:"parameter_status,omitempty"`
- ParseComplete pgproto3.ParseComplete `yaml:"-"`
- ParseCompletes []pgproto3.ParseComplete `json:"parse_complete,omitempty" yaml:"parse_complete,omitempty"`
- ReadyForQuery pgproto3.ReadyForQuery `json:"ready_for_query,omitempty" yaml:"ready_for_query,omitempty"`
- RowDescription pgproto3.RowDescription `json:"row_description,omitempty" yaml:"row_description,omitempty,flow"`
- PortalSuspended pgproto3.PortalSuspended `json:"portal_suspended,omitempty" yaml:"portal_suspended,omitempty"`
- MsgType byte `json:"msg_type,omitempty" yaml:"msg_type,omitempty"`
- AuthType int32 `json:"auth_type" yaml:"auth_type"`
- // AuthMechanism string `json:"auth_mechanism,omitempty" yaml:"auth_mechanism,omitempty"`
- BodyLen int `json:"body_len,omitempty" yaml:"body_len,omitempty"`
-}
-
-type StartupPacket struct {
- Length uint32
- ProtocolVersion uint32
-}
-
-type RegularPacket struct {
- Identifier byte
- Length uint32
- Payload []byte
+ // CopyBothResponse pgproto3.CopyBothResponse `json:"copy_both_response,omitempty" yaml:"copy_both_response,omitempty"`
+ CopyData pgproto3.CopyData `json:"copy_data,omitempty" yaml:"copy_data,omitempty"`
+ // CopyInResponse pgproto3.CopyInResponse `json:"copy_in_response,omitempty" yaml:"copy_in_response,omitempty"`
+ // CopyOutResponse pgproto3.CopyOutResponse `json:"copy_out_response,omitempty" yaml:"copy_out_response,omitempty"`
+ CopyDone pgproto3.CopyDone `json:"copy_done,omitempty" yaml:"copy_done,omitempty"`
+ DataRow pgproto3.DataRow `json:"-" yaml:"-"`
+ DataRows []pgproto3.DataRow `json:"data_row,omitempty" yaml:"data_row,omitempty,flow"`
+ EmptyQueryResponse pgproto3.EmptyQueryResponse `json:"empty_query_response,omitempty" yaml:"empty_query_response,omitempty"`
+ ErrorResponse pgproto3.ErrorResponse `json:"error_response,omitempty" yaml:"error_response,omitempty"`
+ FunctionCallResponse pgproto3.FunctionCallResponse `json:"function_call_response,omitempty" yaml:"function_call_response,omitempty"`
+ NoData pgproto3.NoData `json:"no_data,omitempty" yaml:"no_data,omitempty"`
+ NoticeResponse pgproto3.NoticeResponse `json:"notice_response,omitempty" yaml:"notice_response,omitempty"`
+ NotificationResponse pgproto3.NotificationResponse `json:"notification_response,omitempty" yaml:"notification_response,omitempty"`
+ ParameterDescription pgproto3.ParameterDescription `json:"parameter_description,omitempty" yaml:"parameter_description,omitempty"`
+ ParameterStatus pgproto3.ParameterStatus `json:"-" yaml:"-"`
+ ParameterStatusCombined []pgproto3.ParameterStatus `json:"parameter_status,omitempty" yaml:"parameter_status,omitempty"`
+ ParseComplete pgproto3.ParseComplete `json:"-" yaml:"-"`
+ ParseCompletes []pgproto3.ParseComplete `json:"parse_complete,omitempty" yaml:"parse_complete,omitempty"`
+ ReadyForQuery pgproto3.ReadyForQuery `json:"ready_for_query,omitempty" yaml:"ready_for_query,omitempty"`
+ RowDescription pgproto3.RowDescription `json:"row_description,omitempty" yaml:"row_description,omitempty,flow"`
+ PortalSuspended pgproto3.PortalSuspended `json:"portal_suspended,omitempty" yaml:"portal_suspended,omitempty"`
+ MsgType byte `json:"msg_type,omitempty" yaml:"msg_type,omitempty"`
+ AuthType int32 `json:"auth_type" yaml:"auth_type"`
+ BodyLen int `json:"body_len,omitempty" yaml:"body_len,omitempty"`
}
diff --git a/pkg/platform/docker/docker.go b/pkg/platform/docker/docker.go
index 56b7d7436b..75da42e212 100644
--- a/pkg/platform/docker/docker.go
+++ b/pkg/platform/docker/docker.go
@@ -154,7 +154,7 @@ func (idc *Impl) StopAndRemoveDockerContainer() error {
}
}
- removeOptions := types.ContainerRemoveOptions{
+ removeOptions := dockerContainerPkg.RemoveOptions{
RemoveVolumes: true,
Force: true,
}
@@ -360,7 +360,7 @@ func (idc *Impl) GetHostWorkingDirectory() (string, error) {
// Loop through container mounts and find the mount for current directory in the container
for _, mount := range containerMounts {
if mount.Destination == curDir {
- idc.logger.Debug(fmt.Sprintf("found mount for %s in keploy-v2 container", curDir), zap.Any("mount", mount))
+ idc.logger.Info(fmt.Sprintf("found mount for %s in keploy-v2 container", curDir), zap.Any("mount", mount))
return mount.Source, nil
}
}
@@ -521,6 +521,30 @@ func (idc *Impl) SetKeployNetwork(c *Compose) (*NetworkInfo, error) {
return networkInfo, nil
}
+func (idc *Impl) SetInitPid(c *Compose) error {
+ // Add or modify network for each service
+ for _, service := range c.Services.Content {
+ pidFound := false
+ for _, item := range service.Content {
+ if item.Value == "pid" {
+ pidFound = true
+ break
+ }
+ }
+
+ if !pidFound {
+ service.Content = append(service.Content,
+ &yaml.Node{Kind: yaml.ScalarNode, Value: "pid"},
+ &yaml.Node{
+ Kind: yaml.ScalarNode,
+ Value: "container:keploy-init",
+ },
+ )
+ }
+ }
+ return nil
+}
+
// IsContainerRunning check if the container is already running or not, required for docker start command.
func (idc *Impl) IsContainerRunning(containerName string) (bool, error) {
diff --git a/pkg/platform/docker/service.go b/pkg/platform/docker/service.go
index d41dea9263..cb20878205 100644
--- a/pkg/platform/docker/service.go
+++ b/pkg/platform/docker/service.go
@@ -27,7 +27,7 @@ type Client interface {
SetKeployNetwork(c *Compose) (*NetworkInfo, error)
ReadComposeFile(filePath string) (*Compose, error)
WriteComposeFile(compose *Compose, path string) error
-
+ SetInitPid(c *Compose) error
IsContainerRunning(containerName string) (bool, error)
CreateVolume(ctx context.Context, volumeName string, recreate bool) error
}
diff --git a/pkg/platform/docker/util.go b/pkg/platform/docker/util.go
index f35f46ef73..20cc8f2595 100644
--- a/pkg/platform/docker/util.go
+++ b/pkg/platform/docker/util.go
@@ -1,14 +1,34 @@
-//go:build linux
+//go:build !windows
package docker
import (
+ "context"
+ "errors"
"fmt"
+ "os"
+ "os/exec"
"regexp"
+ "runtime"
+ "strings"
+ "syscall"
+ "github.com/docker/docker/api/types/network"
+ "go.keploy.io/server/v2/config"
"go.keploy.io/server/v2/utils"
+ "go.uber.org/zap"
+ "golang.org/x/term"
)
+type ConfigStruct struct {
+ DockerImage string
+ Envs map[string]string
+}
+
+var DockerConfig = ConfigStruct{
+ DockerImage: "ghcr.io/keploy/keploy",
+}
+
func ParseDockerCmd(cmd string, kind utils.CmdType, idc Client) (string, string, error) {
// Regular expression patterns
@@ -51,3 +71,258 @@ func ParseDockerCmd(cmd string, kind utils.CmdType, idc Client) (string, string,
return containerName, networkName, nil
}
+
+func GenerateDockerEnvs(config ConfigStruct) string {
+ var envs []string
+ for key, value := range config.Envs {
+ envs = append(envs, fmt.Sprintf("-e %s='%s'", key, value))
+ }
+ return strings.Join(envs, " ")
+}
+
+// StartInDocker will check if the docker command is provided as an input
+// then start the Keploy as a docker container and run the command
+// should also return a boolean if the execution is moved to docker
+func StartInDocker(ctx context.Context, logger *zap.Logger, conf *config.Config) error {
+
+ if DockerConfig.Envs == nil {
+ DockerConfig.Envs = map[string]string{
+ "INSTALLATION_ID": conf.InstallationID,
+ }
+ } else {
+ DockerConfig.Envs["INSTALLATION_ID"] = conf.InstallationID
+ }
+
+ //Check if app command starts with docker or docker-compose.
+ // If it does, then we would run the docker version of keploy and
+
+ // pass the all the commands and args to the docker version of Keploy
+
+ err := RunInDocker(ctx, logger)
+ if err != nil {
+ utils.LogError(logger, err, "failed to run the command in docker")
+ return err
+ }
+ // gracefully exit the current process
+ logger.Info("exiting the current process as the command is moved to docker")
+ os.Exit(0)
+ return nil
+}
+
+func RunInDocker(ctx context.Context, logger *zap.Logger) error {
+ //Get the correct keploy alias.
+ keployAlias, err := getAlias(ctx, logger)
+ if err != nil {
+ return err
+ }
+ fmt.Println("Running in docker keploy alias", keployAlias)
+
+ client, err := New(logger)
+ if err != nil {
+ utils.LogError(logger, err, "failed to initalise docker")
+ return err
+ }
+
+ addKeployNetwork(ctx, logger, client)
+ err = client.CreateVolume(ctx, "debugfs", true)
+ if err != nil {
+ utils.LogError(logger, err, "failed to debugfs volume")
+ return err
+ }
+
+ var cmd *exec.Cmd
+
+ // Detect the operating system
+ if runtime.GOOS == "windows" {
+ var args []string
+ args = append(args, "/C")
+ args = append(args, strings.Split(keployAlias, " ")...)
+ args = append(args, os.Args[1:]...)
+ // Use cmd.exe /C for Windows
+ cmd = exec.CommandContext(
+ ctx,
+ "cmd.exe",
+ args...,
+ )
+ } else {
+ // Use sh -c for Unix-like systems
+ cmd = exec.CommandContext(
+ ctx,
+ "sh",
+ "-c",
+ keployAlias,
+ )
+ }
+
+ cmd.Cancel = func() error {
+ err := utils.SendSignal(logger, -cmd.Process.Pid, syscall.SIGINT)
+ if err != nil {
+ utils.LogError(logger, err, "failed to start stop docker")
+ return err
+ }
+ return nil
+ }
+
+ cmd.Stdout = os.Stdout
+ cmd.Stdin = os.Stdin
+ cmd.Stderr = os.Stderr
+
+ logger.Info("running the following command in docker", zap.String("command", cmd.String()))
+ err = cmd.Run()
+ if err != nil {
+ if ctx.Err() == context.Canceled {
+ return ctx.Err()
+ }
+ utils.LogError(logger, err, "failed to start keploy in docker")
+ return err
+ }
+ return nil
+}
+
+func getAlias(ctx context.Context, logger *zap.Logger) (string, error) {
+ // Get the name of the operating system.
+ osName := runtime.GOOS
+ //TODO: configure the hardcoded port mapping
+ img := DockerConfig.DockerImage + ":v" + utils.Version
+ logger.Info("Starting keploy in docker with image", zap.String("image:", img))
+ envs := GenerateDockerEnvs(DockerConfig)
+ if envs != "" {
+ envs = envs + " "
+ }
+ var ttyFlag string
+
+ if term.IsTerminal(int(os.Stdin.Fd())) {
+ // ttyFlag = " -it "
+ ttyFlag = " "
+ } else {
+ ttyFlag = " "
+ }
+
+ switch osName {
+ case "linux":
+ alias := "sudo docker container run --name keploy-v2 " + envs + "-e BINARY_TO_DOCKER=true -p 36789:36789 -p 8096:8096 --privileged --pid=host" + ttyFlag + " -v " + os.Getenv("PWD") + ":" + os.Getenv("PWD") + " -w " + os.Getenv("PWD") + " -v /sys/fs/cgroup:/sys/fs/cgroup -v /sys/kernel/debug:/sys/kernel/debug -v /sys/fs/bpf:/sys/fs/bpf -v /var/run/docker.sock:/var/run/docker.sock -v " + os.Getenv("HOME") + "/.keploy-config:/root/.keploy-config -v " + os.Getenv("HOME") + "/.keploy:/root/.keploy --rm " + img
+ return alias, nil
+ case "windows":
+ // Get the current working directory
+ pwd, err := os.Getwd()
+ if err != nil {
+ utils.LogError(logger, err, "failed to get the current working directory")
+ }
+ dpwd := convertPathToUnixStyle(pwd)
+ cmd := exec.CommandContext(ctx, "docker", "context", "ls", "--format", "{{.Name}}\t{{.Current}}")
+ out, err := cmd.Output()
+ if err != nil {
+ utils.LogError(logger, err, "failed to get the current docker context")
+ return "", errors.New("failed to get alias")
+ }
+ dockerContext := strings.Split(strings.TrimSpace(string(out)), "\n")[0]
+ if len(dockerContext) == 0 {
+ utils.LogError(logger, nil, "failed to get the current docker context")
+ return "", errors.New("failed to get alias")
+ }
+ dockerContext = strings.Split(dockerContext, "\n")[0]
+ if dockerContext == "colima" {
+ logger.Info("Starting keploy in docker with colima context, as that is the current context.")
+ alias := "docker container run --name keploy-v2 " + envs + "-e BINARY_TO_DOCKER=true -p 36789:36789 -p 8096:8096 --privileged --pid=host" + ttyFlag + "-v " + pwd + ":" + dpwd + " -w " + dpwd + " -v /sys/fs/cgroup:/sys/fs/cgroup -v /sys/kernel/debug:/sys/kernel/debug -v /sys/fs/bpf:/sys/fs/bpf -v /var/run/docker.sock:/var/run/docker.sock -v " + os.Getenv("USERPROFILE") + "\\.keploy-config:/root/.keploy-config -v " + os.Getenv("USERPROFILE") + "\\.keploy:/root/.keploy --rm " + img
+ return alias, nil
+ }
+ // if default docker context is used
+ logger.Info("Starting keploy in docker with default context, as that is the current context.")
+ alias := "docker container run --name keploy-v2 " + envs + "-e BINARY_TO_DOCKER=true -p 36789:36789 -p 8096:8096 --privileged --pid=host" + ttyFlag + "-v " + pwd + ":" + dpwd + " -w " + dpwd + " -v /sys/fs/cgroup:/sys/fs/cgroup -v debugfs:/sys/kernel/debug:rw -v /sys/fs/bpf:/sys/fs/bpf -v /var/run/docker.sock:/var/run/docker.sock -v " + os.Getenv("USERPROFILE") + "\\.keploy-config:/root/.keploy-config -v " + os.Getenv("USERPROFILE") + "\\.keploy:/root/.keploy --rm " + img
+ return alias, nil
+ case "darwin":
+ cmd := exec.CommandContext(ctx, "docker", "context", "ls", "--format", "{{.Name}}\t{{.Current}}")
+ out, err := cmd.Output()
+ if err != nil {
+ utils.LogError(logger, err, "failed to get the current docker context")
+ return "", errors.New("failed to get alias")
+ }
+ dockerContext := strings.Split(strings.TrimSpace(string(out)), "\n")[0]
+ if len(dockerContext) == 0 {
+ utils.LogError(logger, nil, "failed to get the current docker context")
+ return "", errors.New("failed to get alias")
+ }
+ dockerContext = strings.Split(dockerContext, "\n")[0]
+ if dockerContext == "colima" {
+ logger.Info("Starting keploy in docker with colima context, as that is the current context.")
+ alias := "docker container run --name keploy-v2 " + envs + "-e BINARY_TO_DOCKER=true -p 36789:36789 -p 8096:8096 --privileged --pid=host" + ttyFlag + "-v " + os.Getenv("PWD") + ":" + os.Getenv("PWD") + " -w " + os.Getenv("PWD") + " -v /sys/fs/cgroup:/sys/fs/cgroup -v /sys/kernel/debug:/sys/kernel/debug -v /sys/fs/bpf:/sys/fs/bpf -v /var/run/docker.sock:/var/run/docker.sock -v " + os.Getenv("HOME") + "/.keploy-config:/root/.keploy-config -v " + os.Getenv("HOME") + "/.keploy:/root/.keploy --rm " + img
+ return alias, nil
+ }
+ // if default docker context is used
+ logger.Info("Starting keploy in docker with default context, as that is the current context.")
+ alias := "docker container run --name keploy-v2 " + envs + "-e BINARY_TO_DOCKER=true -p 36789:36789 -p 8096:8096 --privileged --pid=host" + ttyFlag + "-v " + os.Getenv("PWD") + ":" + os.Getenv("PWD") + " -w " + os.Getenv("PWD") + " -v /sys/fs/cgroup:/sys/fs/cgroup -v debugfs:/sys/kernel/debug:rw -v /sys/fs/bpf:/sys/fs/bpf -v /var/run/docker.sock:/var/run/docker.sock -v " + os.Getenv("HOME") + "/.keploy-config:/root/.keploy-config -v " + os.Getenv("HOME") + "/.keploy:/root/.keploy --rm " + img
+ return alias, nil
+ }
+ return "", errors.New("failed to get alias")
+}
+
+func addKeployNetwork(ctx context.Context, logger *zap.Logger, client Client) {
+
+ // Check if the 'keploy-network' network exists
+ networks, err := client.NetworkList(ctx, network.ListOptions{})
+ if err != nil {
+ logger.Debug("failed to list docker networks")
+ return
+ }
+
+ for _, network := range networks {
+ if network.Name == "keploy-network" {
+ logger.Debug("keploy network already exists")
+ return
+ }
+ }
+
+ // Create the 'keploy' network if it doesn't exist
+ _, err = client.NetworkCreate(ctx, "keploy-network", network.CreateOptions{})
+ if err != nil {
+ logger.Debug("failed to create keploy network")
+ return
+ }
+
+ logger.Debug("keploy network created")
+}
+
+func convertPathToUnixStyle(path string) string {
+ // Replace backslashes with forward slashes
+ unixPath := strings.Replace(path, "\\", "/", -1)
+ // Remove 'C:'
+ if len(unixPath) > 1 && unixPath[1] == ':' {
+ unixPath = unixPath[2:]
+ }
+ return unixPath
+}
+
+// ExtractPidNamespaceInode extracts the inode of the PID namespace of a given PID
+func ExtractPidNamespaceInode(pid int) (string, error) {
+ // Check the OS
+ if runtime.GOOS != "linux" {
+ // Execute command in the container to get the PID namespace
+ output, err := exec.Command("docker", "exec", "keploy-init", "stat", "/proc/1/ns/pid").Output()
+ if err != nil {
+ return "", err
+ }
+ outputStr := string(output)
+
+ // Use a regular expression to extract the inode from the output
+ re := regexp.MustCompile(`pid:\[(\d+)\]`)
+ match := re.FindStringSubmatch(outputStr)
+
+ if len(match) < 2 {
+ return "", fmt.Errorf("failed to extract PID namespace inode")
+ }
+
+ pidNamespace := match[1]
+ return pidNamespace, nil
+ }
+
+ // Check the namespace file in /proc
+ nsPath := fmt.Sprintf("/proc/%d/ns/pid", pid)
+ fileInfo, err := os.Stat(nsPath)
+ if err != nil {
+ return "", err
+ }
+
+ // Retrieve inode number
+ inode := fileInfo.Sys().(*syscall.Stat_t).Ino
+ return fmt.Sprintf("%d", inode), nil
+}
diff --git a/pkg/platform/http/agent.go b/pkg/platform/http/agent.go
new file mode 100644
index 0000000000..56fca21b72
--- /dev/null
+++ b/pkg/platform/http/agent.go
@@ -0,0 +1,649 @@
+//go:build !windows
+
+package http
+
+// Instrumentation package client code,
+
+// send the payload to the server
+// enable http chunking/straeming for large payloads
+
+// setup ki call jo agent start karte hi hogi - it will return nothing.
+// docker k liye alag se setup hoga (can setup via agent flag)
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+ "os/exec"
+ "runtime"
+ "strconv"
+ "sync"
+ "syscall"
+ "time"
+
+ "go.keploy.io/server/v2/config"
+ "go.keploy.io/server/v2/pkg/core/app"
+ "go.keploy.io/server/v2/pkg/core/hooks"
+ "go.keploy.io/server/v2/pkg/models"
+ kdocker "go.keploy.io/server/v2/pkg/platform/docker"
+ "go.keploy.io/server/v2/utils"
+ "go.uber.org/zap"
+ "golang.org/x/sync/errgroup"
+)
+
+type AgentClient struct {
+ logger *zap.Logger
+ dockerClient kdocker.Client //embedding the docker client to transfer the docker client methods to the core object
+ id utils.AutoInc
+ apps sync.Map
+ proxyStarted bool
+ client http.Client
+ conf *config.Config
+}
+
+// this will be the client side implementation
+func New(logger *zap.Logger, client kdocker.Client, c *config.Config) *AgentClient {
+
+ return &AgentClient{
+ logger: logger,
+ dockerClient: client,
+ client: http.Client{},
+ conf: c,
+ }
+}
+
+func (a *AgentClient) GetIncoming(ctx context.Context, id uint64, opts models.IncomingOptions) (<-chan *models.TestCase, error) {
+ requestBody := models.IncomingReq{
+ IncomingOptions: opts,
+ ClientID: id,
+ }
+
+ requestJSON, err := json.Marshal(requestBody)
+ if err != nil {
+ utils.LogError(a.logger, err, "failed to marshal request body for incoming request")
+ return nil, fmt.Errorf("error marshaling request body for incoming request: %s", err.Error())
+ }
+
+ req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("http://localhost:%d/agent/incoming", a.conf.Agent.Port), bytes.NewBuffer(requestJSON))
+ if err != nil {
+ utils.LogError(a.logger, err, "failed to create request for incoming request")
+ return nil, fmt.Errorf("error creating request for incoming request: %s", err.Error())
+ }
+ req.Header.Set("Content-Type", "application/json")
+
+ // Make the HTTP request
+ res, err := a.client.Do(req)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get incoming: %s", err.Error())
+ }
+
+ // Ensure response body is closed when we're done
+ go func() {
+ <-ctx.Done()
+ if res.Body != nil {
+ _ = res.Body.Close()
+ }
+ }()
+
+ // Create a channel to stream TestCase data
+ tcChan := make(chan *models.TestCase)
+
+ go func() {
+ defer close(tcChan)
+ defer func() {
+ err := res.Body.Close()
+ if err != nil {
+ utils.LogError(a.logger, err, "failed to close response body for incoming request")
+ }
+ }()
+
+ decoder := json.NewDecoder(res.Body)
+ fmt.Println("Starting to read from the response body")
+
+ for {
+ var testCase models.TestCase
+ if err := decoder.Decode(&testCase); err != nil {
+ if err == io.EOF || err == io.ErrUnexpectedEOF {
+ // End of the stream
+ break
+ }
+ utils.LogError(a.logger, err, "failed to decode test case from stream")
+ break
+ }
+
+ select {
+ case <-ctx.Done():
+ // If the context is done, exit the loop
+ return
+ case tcChan <- &testCase:
+ // Send the decoded test case to the channel
+ }
+ }
+ }()
+
+ return tcChan, nil
+}
+
+func (a *AgentClient) GetOutgoing(ctx context.Context, id uint64, opts models.OutgoingOptions) (<-chan *models.Mock, error) {
+ requestBody := models.OutgoingReq{
+ OutgoingOptions: opts,
+ ClientID: id,
+ }
+
+ requestJSON, err := json.Marshal(requestBody)
+ if err != nil {
+ utils.LogError(a.logger, err, "failed to marshal request body for mock outgoing")
+ return nil, fmt.Errorf("error marshaling request body for mock outgoing: %s", err.Error())
+ }
+
+ req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("http://localhost:%d/agent/outgoing", a.conf.Agent.Port), bytes.NewBuffer(requestJSON))
+ if err != nil {
+ utils.LogError(a.logger, err, "failed to create request for mock outgoing")
+ return nil, fmt.Errorf("error creating request for mock outgoing: %s", err.Error())
+ }
+ req.Header.Set("Content-Type", "application/json")
+
+ // Make the HTTP request
+ res, err := a.client.Do(req)
+ if err != nil {
+ return nil, fmt.Errorf("failed to authenticate: %s", err.Error())
+ }
+
+ // Create a channel to stream Mock data
+ mockChan := make(chan *models.Mock)
+
+ go func() {
+ defer close(mockChan)
+ defer func() {
+ err := res.Body.Close()
+ if err != nil {
+ utils.LogError(a.logger, err, "failed to close response body for mock outgoing")
+ }
+ }()
+
+ decoder := json.NewDecoder(res.Body)
+
+ // Read from the response body as a stream
+ for {
+ var mock models.Mock
+ if err := decoder.Decode(&mock); err != nil {
+ if err == io.EOF || err == io.ErrUnexpectedEOF {
+ // End of the stream
+ break
+ }
+ utils.LogError(a.logger, err, "failed to decode mock from stream")
+ // break, it will exit the loop if there is any decoding error from the stream
+ }
+
+ select {
+ case <-ctx.Done():
+ // If the context is done, exit the loop
+ return
+ case mockChan <- &mock:
+ // Send the decoded mock to the channel
+ fmt.Println("Sending mock to the channel")
+ fmt.Printf("Mock recieved at client: %v\n", mock)
+ // if mock.Spec.MySQLResponses {
+
+ // }
+ }
+ }
+ }()
+
+ return mockChan, nil
+}
+
+func (a *AgentClient) MockOutgoing(ctx context.Context, id uint64, opts models.OutgoingOptions) error {
+ // make a request to the server to mock outgoing
+ requestBody := models.OutgoingReq{
+ OutgoingOptions: opts,
+ ClientID: id,
+ }
+
+ requestJSON, err := json.Marshal(requestBody)
+ if err != nil {
+ utils.LogError(a.logger, err, "failed to marshal request body for mock outgoing")
+ return fmt.Errorf("error marshaling request body for mock outgoing: %s", err.Error())
+ }
+
+ // mock outgoing request
+ req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("http://localhost:%d/agent/mock", a.conf.Agent.Port), bytes.NewBuffer(requestJSON))
+ if err != nil {
+ utils.LogError(a.logger, err, "failed to create request for mock outgoing")
+ return fmt.Errorf("error creating request for mock outgoing: %s", err.Error())
+ }
+ req.Header.Set("Content-Type", "application/json")
+
+ // Make the HTTP request
+ res, err := a.client.Do(req)
+ if err != nil {
+ return fmt.Errorf("failed to send request for mockOutgoing: %s", err.Error())
+ }
+
+ var mockResp models.AgentResp
+ err = json.NewDecoder(res.Body).Decode(&mockResp)
+ if err != nil {
+ return fmt.Errorf("failed to decode response body for mock outgoing: %s", err.Error())
+ }
+
+ if mockResp.Error != nil {
+ return mockResp.Error
+ }
+
+ return nil
+
+}
+
+func (a *AgentClient) SetMocks(ctx context.Context, id uint64, filtered []*models.Mock, unFiltered []*models.Mock) error {
+ requestBody := models.SetMocksReq{
+ Filtered: filtered,
+ UnFiltered: unFiltered,
+ AppId: id,
+ }
+
+ requestJSON, err := json.Marshal(requestBody)
+ if err != nil {
+ utils.LogError(a.logger, err, "failed to marshal request body for setmocks")
+ return fmt.Errorf("error marshaling request body for setmocks: %s", err.Error())
+ }
+
+ // mock outgoing request
+ req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("http://localhost:%d/agent/setmocks", a.conf.Agent.Port), bytes.NewBuffer(requestJSON))
+ if err != nil {
+ utils.LogError(a.logger, err, "failed to create request for setmocks outgoing")
+ return fmt.Errorf("error creating request for set mocks: %s", err.Error())
+ }
+ req.Header.Set("Content-Type", "application/json")
+
+ // Make the HTTP request
+ res, err := a.client.Do(req)
+ if err != nil {
+ return fmt.Errorf("failed to send request for setmocks: %s", err.Error())
+ }
+
+ var mockResp models.AgentResp
+ err = json.NewDecoder(res.Body).Decode(&mockResp)
+ if err != nil {
+ return fmt.Errorf("failed to decode response body for setmocks: %s", err.Error())
+ }
+
+ if mockResp.Error != nil {
+ return mockResp.Error
+ }
+
+ return nil
+}
+
+func (a *AgentClient) GetConsumedMocks(ctx context.Context, id uint64) ([]string, error) {
+ // Create the URL with query parameters
+ url := fmt.Sprintf("http://localhost:%d/agent/consumedmocks?id=%d", a.conf.Agent.Port, id)
+
+ // Create a new GET request with the query parameter
+ req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create request: %s", err.Error())
+ }
+
+ req.Header.Set("Content-Type", "application/json")
+
+ res, err := a.client.Do(req)
+ if err != nil {
+ return nil, fmt.Errorf("failed to send request for mockOutgoing: %s", err.Error())
+ }
+
+ defer func() {
+ err := res.Body.Close()
+ if err != nil {
+ utils.LogError(a.logger, err, "failed to close response body for getconsumedmocks")
+ }
+ }()
+
+ var consumedMocks []string
+ err = json.NewDecoder(res.Body).Decode(&consumedMocks)
+ if err != nil {
+ return nil, fmt.Errorf("failed to decode response body: %s", err.Error())
+ }
+
+ return consumedMocks, nil
+}
+
+func (a *AgentClient) UnHook(_ context.Context, _ uint64) error {
+ return nil
+}
+
+func (a *AgentClient) GetContainerIP(_ context.Context, id uint64) (string, error) {
+
+ app, err := a.getApp(id)
+ if err != nil {
+ utils.LogError(a.logger, err, "failed to get app")
+ return "", err
+ }
+
+ ip := app.ContainerIPv4Addr()
+ a.logger.Debug("ip address of the target app container", zap.Any("ip", ip))
+ if ip == "" {
+ return "", fmt.Errorf("failed to get the IP address of the app container. Try increasing --delay (in seconds)")
+ }
+
+ return ip, nil
+}
+
+func (a *AgentClient) Run(ctx context.Context, id uint64, _ models.RunOptions) models.AppError {
+
+ app, err := a.getApp(id)
+ if err != nil {
+ utils.LogError(a.logger, err, "failed to get app while running")
+ return models.AppError{AppErrorType: models.ErrInternal, Err: err}
+ }
+
+ runAppErrGrp, runAppCtx := errgroup.WithContext(ctx)
+
+ appErrCh := make(chan models.AppError, 1)
+
+ defer func() {
+ err := runAppErrGrp.Wait()
+
+ if err != nil {
+ utils.LogError(a.logger, err, "failed to stop the app")
+ }
+ }()
+
+ runAppErrGrp.Go(func() error {
+ defer utils.Recover(a.logger)
+ defer close(appErrCh)
+ appErr := app.Run(runAppCtx)
+ if appErr.Err != nil {
+ utils.LogError(a.logger, appErr.Err, "error while running the app")
+ appErrCh <- appErr
+ }
+ return nil
+ })
+
+ select {
+ case <-runAppCtx.Done():
+ fmt.Println("Context is canceled in the run app function")
+ return models.AppError{AppErrorType: models.ErrCtxCanceled, Err: nil}
+ case appErr := <-appErrCh:
+ return appErr
+ }
+}
+
+func (a *AgentClient) Setup(ctx context.Context, cmd string, opts models.SetupOptions) (uint64, error) {
+
+ // if the agent is not running, start the agent
+ clientID := utils.GenerateID()
+ clientID = 0 // how can I retrieve the same client Id in the testmode ??
+
+ isDockerCmd := utils.IsDockerCmd(utils.CmdType(opts.CommandType))
+
+ isAgentRunning := a.isAgentRunning(ctx)
+
+ if !isAgentRunning {
+ // Start the keploy agent as a detached process and pipe the logs into a file
+ // if !isDockerCmd && runtime.GOOS != "linux" {
+ // return 0, fmt.Errorf("Operating system not supported for this feature")
+ // }
+ if isDockerCmd {
+ // run the docker container instead of the agent binary
+ go func() {
+ if err := a.StartInDocker(ctx, a.logger, clientID); err != nil {
+ a.logger.Error("failed to start docker agent", zap.Error(err))
+ }
+ }()
+ } else {
+ // Open the log file in append mode or create it if it doesn't exist
+ logFile, err := os.OpenFile("keploy_agent.log", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644)
+ if err != nil {
+ utils.LogError(a.logger, err, "failed to open log file")
+ return 0, err
+ }
+
+ defer func() {
+ err := logFile.Close()
+ if err != nil {
+ utils.LogError(a.logger, err, "failed to close agent log file")
+ }
+ }()
+ // TODO: Pass the binary name as an argument while go
+ agentCmd := exec.Command("sudo", "keployv2", "agent")
+ agentCmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} // Detach the process
+
+ // Redirect the standard output and error to the log file
+ agentCmd.Stdout = logFile
+ agentCmd.Stderr = logFile
+ agentCmd.Stdin = os.Stdin
+
+ err = agentCmd.Start()
+ if err != nil {
+ utils.LogError(a.logger, err, "failed to start keploy agent")
+ return 0, err
+ }
+
+ a.logger.Info("keploy agent started", zap.Int("pid", agentCmd.Process.Pid))
+ }
+ }
+
+ time.Sleep(5 * time.Second)
+ // check if its docker then create a init container first
+ // and then the app container
+ usrApp := app.NewApp(a.logger, clientID, cmd, a.dockerClient, app.Options{
+ DockerNetwork: opts.DockerNetwork,
+ Container: opts.Container,
+ DockerDelay: opts.DockerDelay,
+ })
+ a.apps.Store(clientID, usrApp)
+
+ err := usrApp.Setup(ctx)
+ if err != nil {
+ utils.LogError(a.logger, err, "failed to setup app")
+ return 0, err
+ }
+
+ var inode uint64
+ if isDockerCmd {
+ // Start the init container to get the pid namespace
+ inode, err = a.Initcontainer(ctx, a.logger, app.Options{
+ DockerNetwork: opts.DockerNetwork,
+ Container: opts.Container,
+ DockerDelay: opts.DockerDelay,
+ })
+ if err != nil {
+ utils.LogError(a.logger, err, "failed to setup init container")
+ }
+
+ }
+
+ opts.ClientID = clientID
+ opts.AppInode = inode
+ // Register the client with the server
+ err = a.RegisterClient(ctx, opts)
+ if err != nil {
+ utils.LogError(a.logger, err, "failed to register client")
+ return 0, err
+ }
+
+ isAgentRunning = a.isAgentRunning(ctx)
+ if !isAgentRunning {
+ return 0, fmt.Errorf("keploy agent is not running, please start the agent first")
+ }
+
+ return clientID, nil
+}
+
+func (a *AgentClient) getApp(id uint64) (*app.App, error) {
+ ap, ok := a.apps.Load(id)
+ if !ok {
+ return nil, fmt.Errorf("app with id:%v not found", id)
+ }
+
+ // type assertion on the app
+ h, ok := ap.(*app.App)
+ if !ok {
+ return nil, fmt.Errorf("failed to type assert app with id:%v", id)
+ }
+
+ return h, nil
+}
+
+func (a *AgentClient) RegisterClient(ctx context.Context, opts models.SetupOptions) error {
+
+ isAgent := a.isAgentRunning(ctx)
+ if !isAgent {
+ return fmt.Errorf("keploy agent is not running, please start the agent first")
+ }
+
+ // Register the client with the server
+ clientPid := uint32(os.Getpid())
+
+ // start the app container and get the inode number
+ // keploy agent would have already runnning,
+
+ var inode uint64
+ var err error
+ if runtime.GOOS == "linux" {
+ // send the network info to the kernel
+ inode, err = hooks.GetSelfInodeNumber()
+ if err != nil {
+ a.logger.Error("failed to get inode number")
+ }
+ }
+
+ // Register the client with the server
+ requestBody := models.RegisterReq{
+ SetupOptions: models.SetupOptions{
+ DockerNetwork: opts.DockerNetwork,
+ ClientNsPid: clientPid,
+ Mode: opts.Mode,
+ ClientID: 0,
+ ClientInode: inode,
+ IsDocker: a.conf.Agent.IsDocker,
+ AppInode: opts.AppInode,
+ },
+ }
+
+ requestJSON, err := json.Marshal(requestBody)
+ if err != nil {
+ utils.LogError(a.logger, err, "failed to marshal request body for register client")
+ return fmt.Errorf("error marshaling request body for register client: %s", err.Error())
+ }
+
+ resp, err := a.client.Post(fmt.Sprintf("http://localhost:%d/agent/register", a.conf.Agent.Port), "application/json", bytes.NewBuffer(requestJSON))
+ if err != nil {
+ utils.LogError(a.logger, err, "failed to send register client request")
+ return fmt.Errorf("error sending register client request: %s", err.Error())
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ return fmt.Errorf("failed to register client: %s", resp.Status)
+ }
+
+ // TODO: Read the response body in which we return the app id
+ var RegisterResp models.AgentResp
+ err = json.NewDecoder(resp.Body).Decode(&RegisterResp)
+ if err != nil {
+ utils.LogError(a.logger, err, "failed to decode response body for register client")
+ return fmt.Errorf("error decoding response body for register client: %s", err.Error())
+ }
+
+ if RegisterResp.Error != nil {
+ return RegisterResp.Error
+ }
+
+ return nil
+}
+
+func (a *AgentClient) StartInDocker(ctx context.Context, logger *zap.Logger, clientId uint64) error {
+
+ // Start the keploy agent in a Docker container
+ agentCtx := context.WithoutCancel(context.Background())
+
+ err := kdocker.StartInDocker(agentCtx, logger, &config.Config{
+ InstallationID: a.conf.InstallationID,
+ })
+ if err != nil {
+ utils.LogError(logger, err, "failed to start keploy agent in docker")
+ return err
+ }
+ return nil
+}
+
+func (a *AgentClient) Initcontainer(ctx context.Context, logger *zap.Logger, opts app.Options) (uint64, error) {
+ // Start the init container to get the PID namespace inode
+ cmdCancel := func(cmd *exec.Cmd) func() error {
+ return func() error {
+ a.logger.Info("sending SIGINT to the Initcontainer", zap.Any("cmd.Process.Pid", cmd.Process.Pid))
+ err := utils.SendSignal(a.logger, -cmd.Process.Pid, syscall.SIGINT)
+ return err
+ }
+ }
+
+ //change the permissions of the init.sh file
+ // err := exec.Command("chmod", "+x", "initStop.sh").Run()
+ // if err != nil {
+ // a.logger.Error("failed to change the permissions of the initStop.sh file", zap.Error(err))
+ // return 0, err
+ // }
+
+ cmd := fmt.Sprintf("docker run --network=%s --name keploy-init --rm -v$(pwd)/initStop.sh:/initStop.sh alpine /initStop.sh", a.conf.NetworkName)
+
+ // execute the command
+ //get the errorgroup from the context
+ grp, ok := ctx.Value(models.ErrGroupKey).(*errgroup.Group)
+ if !ok {
+ return 0, fmt.Errorf("failed to get errorgroup from the context")
+ }
+
+ grp.Go(func() error {
+ println("Executing the init container command")
+ cmdErr := utils.ExecuteCommand(ctx, a.logger, cmd, cmdCancel, 25*time.Second)
+ if cmdErr.Err != nil && cmdErr.Type == utils.Init {
+ utils.LogError(a.logger, cmdErr.Err, "failed to execute init container command")
+ }
+
+ println("Init container stopped")
+ return nil
+ })
+
+ time.Sleep(3 * time.Second)
+ // Get the PID of the container's first process
+ inspect, err := a.dockerClient.ContainerInspect(ctx, "keploy-init")
+ if err != nil {
+ a.logger.Error("failed to inspect container", zap.Error(err))
+ return 0, err
+ }
+
+ pid := inspect.State.Pid
+ a.logger.Info("Container PID", zap.Int("pid", pid))
+
+ // Extract inode from the PID namespace
+ pidNamespaceInode, err := kdocker.ExtractPidNamespaceInode(pid)
+ if err != nil {
+ a.logger.Error("failed to extract PID namespace inode", zap.Error(err))
+ return 0, err
+ }
+
+ a.logger.Info("PID Namespace Inode", zap.String("inode", pidNamespaceInode))
+ iNode, err := strconv.ParseUint(pidNamespaceInode, 10, 64)
+ return iNode, nil
+}
+
+func (a *AgentClient) isAgentRunning(ctx context.Context) bool {
+
+ req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("http://localhost:%d/agent/health", a.conf.Agent.Port), nil)
+ if err != nil {
+ utils.LogError(a.logger, err, "failed to send request to the agent server")
+ }
+
+ resp, err := a.client.Do(req)
+ if err != nil {
+ a.logger.Info("Keploy agent is not running in background, starting the agent")
+ return false
+ } else {
+ a.logger.Info("Setup request sent to the server", zap.String("status", resp.Status))
+ return true
+ }
+}
diff --git a/pkg/platform/yaml/configdb/user/db.go b/pkg/platform/yaml/configdb/user/db.go
index 5dc3fae5fa..33b2091221 100644
--- a/pkg/platform/yaml/configdb/user/db.go
+++ b/pkg/platform/yaml/configdb/user/db.go
@@ -89,16 +89,13 @@ func New(logger *zap.Logger, cfg *config.Config) *Db {
func (db *Db) GetInstallationID(_ context.Context) (string, error) {
var id string
var err error
- inDocker := os.Getenv("KEPLOY_INDOCKER")
- if inDocker == "true" {
- id = os.Getenv("INSTALLATION_ID")
- } else {
- id, err = machineid.ID()
- if err != nil {
- db.logger.Debug("failed to get machine id", zap.Error(err))
- return "", nil
- }
+
+ id, err = machineid.ID()
+ if err != nil {
+ db.logger.Debug("failed to get machine id", zap.Error(err))
+ return "", nil
}
+
if id == "" {
db.logger.Debug("got empty machine id")
return "", nil
diff --git a/pkg/platform/yaml/mockdb/util.go b/pkg/platform/yaml/mockdb/util.go
index c570c2b2b8..a4da194c0b 100644
--- a/pkg/platform/yaml/mockdb/util.go
+++ b/pkg/platform/yaml/mockdb/util.go
@@ -174,8 +174,8 @@ func EncodeMock(mock *models.Mock, logger *zap.Logger) (*yaml.NetworkTrafficDoc,
return nil, err
}
default:
- utils.LogError(logger, nil, "failed to marshal the recorded mock into yaml due to invalid kind of mock")
- return nil, errors.New("type of mock is invalid")
+ logger.Debug("failed to marshal the mock input-output as yaml", zap.Any("type", mock.Kind))
+ return nil, errors.New("failed to marshal the mock input-output as yaml")
}
return &yamlDoc, nil
diff --git a/pkg/service/agent/agent.go b/pkg/service/agent/agent.go
new file mode 100644
index 0000000000..e43908aef2
--- /dev/null
+++ b/pkg/service/agent/agent.go
@@ -0,0 +1,288 @@
+//go:build linux
+
+package agent
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "sync"
+
+ "go.keploy.io/server/v2/pkg/core"
+ "go.keploy.io/server/v2/pkg/core/hooks"
+ "go.keploy.io/server/v2/pkg/core/hooks/structs"
+ "go.keploy.io/server/v2/pkg/models"
+ kdocker "go.keploy.io/server/v2/pkg/platform/docker"
+ "go.keploy.io/server/v2/utils"
+ "go.uber.org/zap"
+ "golang.org/x/sync/errgroup"
+)
+
+type Agent struct {
+ logger *zap.Logger
+ core.Proxy // embedding the Proxy interface to transfer the proxy methods to the core object
+ core.Hooks // embedding the Hooks interface to transfer the hooks methods to the core object
+ core.Tester // embedding the Tester interface to transfer the tester methods to the core object
+ dockerClient kdocker.Client //embedding the docker client to transfer the docker client methods to the core object
+ id utils.AutoInc
+ apps sync.Map
+ proxyStarted bool
+}
+
+func New(logger *zap.Logger, hook core.Hooks, proxy core.Proxy, tester core.Tester, client kdocker.Client) *Agent {
+ return &Agent{
+ logger: logger,
+ Hooks: hook,
+ Proxy: proxy,
+ Tester: tester,
+ dockerClient: client,
+ }
+}
+
+// Setup will create a new app and store it in the map, all the setup will be done here
+func (a *Agent) Setup(ctx context.Context, _ string, opts models.SetupOptions) error {
+
+ a.logger.Info("Starting the agent in ", zap.String(string(opts.Mode), "mode"))
+ err := a.Hook(ctx, 0, models.HookOptions{Mode: opts.Mode, IsDocker: opts.IsDocker})
+ if err != nil {
+ a.logger.Error("failed to hook into the app", zap.Error(err))
+ }
+
+ select {
+ case <-ctx.Done():
+ fmt.Println("Context cancelled, stopping Setup")
+ return context.Canceled
+ }
+}
+
+func (a *Agent) GetIncoming(ctx context.Context, id uint64, opts models.IncomingOptions) (<-chan *models.TestCase, error) {
+ return a.Hooks.Record(ctx, id, opts)
+}
+
+func (a *Agent) GetOutgoing(ctx context.Context, id uint64, opts models.OutgoingOptions) (<-chan *models.Mock, error) {
+ m := make(chan *models.Mock, 500)
+
+ err := a.Proxy.Record(ctx, id, m, opts)
+ if err != nil {
+ return nil, err
+ }
+
+ return m, nil
+}
+
+func (a *Agent) MockOutgoing(ctx context.Context, id uint64, opts models.OutgoingOptions) error {
+ a.logger.Info("Inside MockOutgoing of agent binary !!")
+
+ err := a.Proxy.Mock(ctx, id, opts)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (a *Agent) Hook(ctx context.Context, id uint64, opts models.HookOptions) error {
+ hookErr := errors.New("failed to hook into the app")
+
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ default:
+ }
+
+ // create a new error group for the hooks
+ hookErrGrp, _ := errgroup.WithContext(ctx)
+ hookCtx := context.WithoutCancel(ctx) //so that main context doesn't cancel the hookCtx to control the lifecycle of the hooks
+ hookCtx, hookCtxCancel := context.WithCancel(hookCtx)
+ hookCtx = context.WithValue(hookCtx, models.ErrGroupKey, hookErrGrp)
+
+ // create a new error group for the proxy
+ proxyErrGrp, _ := errgroup.WithContext(ctx)
+ proxyCtx := context.WithoutCancel(ctx) //so that main context doesn't cancel the proxyCtx to control the lifecycle of the proxy
+ proxyCtx, proxyCtxCancel := context.WithCancel(proxyCtx)
+ proxyCtx = context.WithValue(proxyCtx, models.ErrGroupKey, proxyErrGrp)
+
+ hookErrGrp.Go(func() error {
+ <-ctx.Done()
+
+ proxyCtxCancel()
+ err := proxyErrGrp.Wait()
+ if err != nil {
+ utils.LogError(a.logger, err, "failed to stop the proxy")
+ }
+
+ hookCtxCancel()
+ err = hookErrGrp.Wait()
+ if err != nil {
+ utils.LogError(a.logger, err, "failed to unload the hooks")
+ }
+ return nil
+ })
+
+ // load hooks if the mode changes ..
+ err := a.Hooks.Load(hookCtx, id, core.HookCfg{
+ AppID: id,
+ Pid: 0,
+ IsDocker: opts.IsDocker,
+ KeployIPV4: "172.18.0.2",
+ Mode: opts.Mode,
+ })
+
+ if err != nil {
+ utils.LogError(a.logger, err, "failed to load hooks")
+ return hookErr
+ }
+
+ if a.proxyStarted {
+ a.logger.Info("Proxy already started")
+ return nil
+ }
+
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ default:
+ }
+
+ // TODO: Hooks can be loaded multiple times but proxy should be started only once
+ // if there is another containerized app, then we need to pass new (ip:port) of proxy to the eBPF
+ // as the network namespace is different for each container and so is the keploy/proxy IP to communicate with the app.
+ err = a.Proxy.StartProxy(proxyCtx, core.ProxyOptions{
+ DNSIPv4Addr: "172.18.0.2",
+ //DnsIPv6Addr: ""
+ })
+ if err != nil {
+ utils.LogError(a.logger, err, "failed to start proxy")
+ return hookErr
+ }
+
+ a.proxyStarted = true
+
+ // For keploy test bench
+ if opts.EnableTesting {
+
+ // enable testing in the app
+ // a.EnableTesting = true
+ // a.Mode = opts.Mode
+
+ // Setting up the test bench
+ err := a.Tester.Setup(ctx, models.TestingOptions{Mode: opts.Mode})
+ if err != nil {
+ utils.LogError(a.logger, err, "error while setting up the test bench environment")
+ return errors.New("failed to setup the test bench")
+ }
+ }
+
+ return nil
+}
+
+func (a *Agent) SetMocks(ctx context.Context, id uint64, filtered []*models.Mock, unFiltered []*models.Mock) error {
+ fmt.Println("Sending Mocks to the Proxy !!")
+ return a.Proxy.SetMocks(ctx, id, filtered, unFiltered)
+}
+
+func (a *Agent) GetConsumedMocks(ctx context.Context, id uint64) ([]string, error) {
+ return a.Proxy.GetConsumedMocks(ctx, id)
+}
+
+func (a *Agent) UnHook(_ context.Context, id uint64) error {
+ return nil
+}
+
+func (a *Agent) RegisterClient(ctx context.Context, opts models.SetupOptions) error {
+ fmt.Println("Registering client with keploy client id opts.AppInode!! ", opts.AppInode)
+ fmt.Println("Registering client with keploy client id opts.ClientInode!! ", opts.ClientInode)
+
+ // send the network info to the kernel
+ err := a.SendNetworkInfo(ctx, opts)
+ if err != nil {
+ a.logger.Error("failed to send network info to the kernel", zap.Error(err))
+ return err
+ }
+
+ clientInfo := structs.ClientInfo{
+ KeployClientNsPid: opts.ClientNsPid,
+ IsDockerApp: 0,
+ KeployClientInode: opts.ClientInode,
+ AppInode: opts.AppInode,
+ }
+
+ switch opts.Mode {
+ case models.MODE_RECORD:
+ clientInfo.Mode = uint32(1)
+ case models.MODE_TEST:
+ clientInfo.Mode = uint32(2)
+ default:
+ clientInfo.Mode = uint32(0)
+ }
+
+ if opts.IsDocker {
+ clientInfo.IsDockerApp = 1
+ }
+
+ return a.Hooks.SendKeployClientInfo(ctx, opts.ClientID, clientInfo)
+}
+
+func (a *Agent) SendNetworkInfo(ctx context.Context, opts models.SetupOptions) error {
+ if !opts.IsDocker {
+ proxyIP, err := hooks.IPv4ToUint32("127.0.0.1")
+ proxyInfo := structs.ProxyInfo{
+ IP4: proxyIP,
+ IP6: [4]uint32{0, 0, 0, 0},
+ Port: 16789,
+ }
+ fmt.Println("PROXY INFO: ", proxyInfo)
+ err = a.Hooks.SendClientProxyInfo(ctx, opts.ClientID, proxyInfo)
+ if err != nil {
+ return err
+ }
+ return nil
+ }
+
+ inspect, err := a.dockerClient.ContainerInspect(ctx, "keploy-v2")
+ if err != nil {
+ utils.LogError(a.logger, nil, fmt.Sprintf("failed to get inspect keploy container:%v", inspect))
+ return err
+ }
+
+ keployNetworks := inspect.NetworkSettings.Networks
+ //Here we considering that the application would use only one custom network.
+ //TODO: handle for application having multiple custom networks
+ //TODO: check the logic for correctness
+ var keployIPv4 string
+ for n, settings := range keployNetworks {
+ if n == opts.DockerNetwork {
+ keployIPv4 = settings.IPAddress // TODO: keployIPv4 needs to be send to the agent
+ // a.logger.Info("Successfully injected network to the keploy container", zap.Any("Keploy container", a.keployContainer), zap.Any("appNetwork", network), zap.String("keploy container ip", a.keployIPv4))
+ break
+ }
+
+ }
+ fmt.Println("Keploy container IP: ", keployIPv4)
+ ipv4, err := hooks.IPv4ToUint32(keployIPv4)
+ if err != nil {
+ return err
+ }
+
+ var ipv6 [4]uint32
+ if opts.IsDocker {
+ ipv6, err := hooks.ToIPv4MappedIPv6(keployIPv4)
+ if err != nil {
+ return fmt.Errorf("failed to convert ipv4:%v to ipv4 mapped ipv6 in docker env:%v", ipv4, err)
+ }
+ a.logger.Debug(fmt.Sprintf("IPv4-mapped IPv6 for %s is: %08x:%08x:%08x:%08x\n", keployIPv4, ipv6[0], ipv6[1], ipv6[2], ipv6[3]))
+
+ }
+
+ proxyInfo := structs.ProxyInfo{
+ IP4: ipv4,
+ IP6: ipv6,
+ Port: 36789,
+ }
+
+ err = a.Hooks.SendClientProxyInfo(ctx, opts.ClientID, proxyInfo)
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/pkg/service/agent/service.go b/pkg/service/agent/service.go
new file mode 100644
index 0000000000..ef0b6eae85
--- /dev/null
+++ b/pkg/service/agent/service.go
@@ -0,0 +1,34 @@
+package agent
+
+import (
+ "context"
+
+ "go.keploy.io/server/v2/pkg/models"
+)
+
+type Service interface {
+ Setup(ctx context.Context, cmd string, opts models.SetupOptions) error
+ GetIncoming(ctx context.Context, id uint64, opts models.IncomingOptions) (<-chan *models.TestCase, error)
+ GetOutgoing(ctx context.Context, id uint64, opts models.OutgoingOptions) (<-chan *models.Mock, error)
+ MockOutgoing(ctx context.Context, id uint64, opts models.OutgoingOptions) error
+ SetMocks(ctx context.Context, id uint64, filtered []*models.Mock, unFiltered []*models.Mock) error
+ GetConsumedMocks(ctx context.Context, id uint64) ([]string, error)
+ RegisterClient(ctx context.Context, opts models.SetupOptions) error
+}
+
+type Options struct {
+ Platform Platform
+ Network string
+ Container string
+ SelfTesting bool
+ Mode models.Mode
+}
+
+type Platform string
+
+var (
+ linux Platform = "linux"
+ windows Platform = "windows"
+ mac Platform = "mac"
+ docker Platform = "docker"
+)
diff --git a/pkg/service/agent/utils.go b/pkg/service/agent/utils.go
new file mode 100644
index 0000000000..75351409e0
--- /dev/null
+++ b/pkg/service/agent/utils.go
@@ -0,0 +1,2 @@
+package agent
+
diff --git a/pkg/service/orchestrator/orchestrator.go b/pkg/service/orchestrator/orchestrator.go
index b07150fbde..16b8533d86 100644
--- a/pkg/service/orchestrator/orchestrator.go
+++ b/pkg/service/orchestrator/orchestrator.go
@@ -1,4 +1,3 @@
-//go:build linux
// Package orchestrator acts as a main brain for both the record and replay services
package orchestrator
diff --git a/pkg/service/orchestrator/rerecord.go b/pkg/service/orchestrator/rerecord.go
index 2f56eb9ccf..33a4e56cb0 100644
--- a/pkg/service/orchestrator/rerecord.go
+++ b/pkg/service/orchestrator/rerecord.go
@@ -1,4 +1,3 @@
-//go:build linux
package orchestrator
@@ -207,7 +206,7 @@ func (o *Orchestrator) replayTests(ctx context.Context, testSet string) (bool, e
if utils.IsDockerCmd(cmdType) {
host = o.config.ContainerName
- userIP, err = o.record.GetContainerIP(ctx, o.config.AppID)
+ userIP, err = o.replay.GetContainerIP(ctx, o.config.AppID)
if err != nil {
utils.LogError(o.logger, err, "failed to get the app ip")
return false, err
diff --git a/pkg/service/record/record.go b/pkg/service/record/record.go
index 78712fa56d..9194fe1421 100755
--- a/pkg/service/record/record.go
+++ b/pkg/service/record/record.go
@@ -1,5 +1,3 @@
-//go:build linux
-
// Package record provides functionality for recording and managing test cases and mocks.
package record
@@ -49,10 +47,11 @@ func (r *Recorder) Start(ctx context.Context, reRecord bool) error {
runAppCtx := context.WithoutCancel(ctx)
runAppCtx, runAppCtxCancel := context.WithCancel(runAppCtx)
- hookErrGrp, _ := errgroup.WithContext(ctx)
- hookCtx := context.WithoutCancel(ctx)
- hookCtx, hookCtxCancel := context.WithCancel(hookCtx)
- hookCtx = context.WithValue(hookCtx, models.ErrGroupKey, hookErrGrp)
+ setupErrGrp, _ := errgroup.WithContext(ctx)
+ setupCtx := context.WithoutCancel(ctx)
+ setupCtx, setupCtxCancel := context.WithCancel(setupCtx)
+ setupCtx = context.WithValue(ctx, models.ErrGroupKey, setupErrGrp)
+
// reRecordCtx, reRecordCancel := context.WithCancel(ctx)
// defer reRecordCancel() // Cancel the context when the function returns
@@ -85,10 +84,11 @@ func (r *Recorder) Start(ctx context.Context, reRecord bool) error {
if err != nil {
utils.LogError(r.logger, err, "failed to stop application")
}
- hookCtxCancel()
- err = hookErrGrp.Wait()
+
+ setupCtxCancel()
+ err = setupErrGrp.Wait()
if err != nil {
- utils.LogError(r.logger, err, "failed to stop hooks")
+ utils.LogError(r.logger, err, "failed to stop setup execution, that covers init container")
}
err = errGrp.Wait()
if err != nil {
@@ -98,8 +98,6 @@ func (r *Recorder) Start(ctx context.Context, reRecord bool) error {
}()
defer close(appErrChan)
- defer close(insertTestErrChan)
- defer close(insertMockErrChan)
newTestSetID, err := r.GetNextTestSetID(ctx)
if err != nil {
@@ -111,14 +109,18 @@ func (r *Recorder) Start(ctx context.Context, reRecord bool) error {
//checking for context cancellation as we don't want to start the instrumentation if the context is cancelled
select {
case <-ctx.Done():
+ fmt.Println("Context cancelled 0")
return nil
default:
}
// Instrument will setup the environment and start the hooks and proxy
- appID, err = r.Instrument(hookCtx)
+ // scope of modularization: This function is defined in the Instrumentation interface
+ //set a value in the context to be used by the instrumentation
+ ctx = context.WithValue(ctx, "debugKey", "keployTesting")
+ appID, err = r.instrumentation.Setup(setupCtx, r.config.Command, models.SetupOptions{Container: r.config.ContainerName, DockerNetwork: r.config.NetworkName, DockerDelay: r.config.BuildDelay, Mode: models.MODE_RECORD, CommandType: r.config.CommandType})
if err != nil {
- stopReason = "failed to instrument the application"
+ stopReason = "failed setting up the environment"
utils.LogError(r.logger, err, stopReason)
return fmt.Errorf(stopReason)
}
@@ -193,6 +195,7 @@ func (r *Recorder) Start(ctx context.Context, reRecord bool) error {
return errors.New("failed to stop recording")
}
case <-ctx.Done():
+ fmt.Println("Context cancelled 1")
return nil
}
return nil
@@ -227,60 +230,62 @@ func (r *Recorder) Start(ctx context.Context, reRecord bool) error {
case err = <-insertMockErrChan:
stopReason = "error while inserting mock into db, hence stopping keploy"
case <-ctx.Done():
+ fmt.Println("Context cancelled 2")
return nil
}
utils.LogError(r.logger, err, stopReason)
return fmt.Errorf(stopReason)
}
-func (r *Recorder) Instrument(ctx context.Context) (uint64, error) {
- var stopReason string
-
- // setting up the environment for recording
- appID, err := r.instrumentation.Setup(ctx, r.config.Command, models.SetupOptions{Container: r.config.ContainerName, DockerNetwork: r.config.NetworkName, DockerDelay: r.config.BuildDelay})
- if err != nil {
- stopReason = "failed setting up the environment"
- utils.LogError(r.logger, err, stopReason)
- return 0, fmt.Errorf(stopReason)
- }
- r.config.AppID = appID
-
- // checking for context cancellation as we don't want to start the hooks and proxy if the context is cancelled
- select {
- case <-ctx.Done():
- return appID, nil
- default:
- // Starting the hooks and proxy
- err = r.instrumentation.Hook(ctx, appID, models.HookOptions{Mode: models.MODE_RECORD, EnableTesting: r.config.EnableTesting, Rules: r.config.BypassRules})
- if err != nil {
- stopReason = "failed to start the hooks and proxy"
- utils.LogError(r.logger, err, stopReason)
- if ctx.Err() == context.Canceled {
- return appID, err
- }
- return appID, fmt.Errorf(stopReason)
- }
- }
- return appID, nil
-}
-
func (r *Recorder) GetTestAndMockChans(ctx context.Context, appID uint64) (FrameChan, error) {
incomingOpts := models.IncomingOptions{
Filters: r.config.Record.Filters,
}
- incomingChan, err := r.instrumentation.GetIncoming(ctx, appID, incomingOpts)
- if err != nil {
- return FrameChan{}, fmt.Errorf("failed to get incoming test cases: %w", err)
- }
outgoingOpts := models.OutgoingOptions{
Rules: r.config.BypassRules,
MongoPassword: r.config.Test.MongoPassword,
FallBackOnMiss: r.config.Test.FallBackOnMiss,
}
- outgoingChan, err := r.instrumentation.GetOutgoing(ctx, appID, outgoingOpts)
- if err != nil {
- return FrameChan{}, fmt.Errorf("failed to get outgoing mocks: %w", err)
+
+ // Create channels to receive incoming and outgoing data
+ incomingChan := make(chan *models.TestCase)
+ outgoingChan := make(chan *models.Mock)
+ errChan := make(chan error, 2)
+
+ go func() {
+ defer close(incomingChan)
+ ch, err := r.instrumentation.GetIncoming(ctx, appID, incomingOpts)
+ if err != nil {
+ errChan <- fmt.Errorf("failed to get incoming test cases: %w", err)
+ return
+ }
+
+ for testCase := range ch {
+ incomingChan <- testCase
+ }
+ }()
+
+ go func() {
+ defer close(outgoingChan)
+ ch, err := r.instrumentation.GetOutgoing(ctx, appID, outgoingOpts)
+ if err != nil {
+ errChan <- fmt.Errorf("failed to get outgoing mocks: %w", err)
+ return
+ }
+
+ for mock := range ch {
+ outgoingChan <- mock
+ }
+ }()
+
+ // Check for errors after starting the goroutines
+ select {
+ case err := <-errChan:
+ // If there's an error, return it immediately
+ return FrameChan{}, err
+ default:
+ // No errors, proceed
}
return FrameChan{
diff --git a/pkg/service/record/service.go b/pkg/service/record/service.go
index 700ca53bf2..263f66e44e 100755
--- a/pkg/service/record/service.go
+++ b/pkg/service/record/service.go
@@ -10,7 +10,6 @@ type Instrumentation interface {
//Setup prepares the environment for the recording
Setup(ctx context.Context, cmd string, opts models.SetupOptions) (uint64, error)
//Hook will load hooks and start the proxy server.
- Hook(ctx context.Context, id uint64, opts models.HookOptions) error
GetIncoming(ctx context.Context, id uint64, opts models.IncomingOptions) (<-chan *models.TestCase, error)
GetOutgoing(ctx context.Context, id uint64, opts models.OutgoingOptions) (<-chan *models.Mock, error)
// Run is blocking call and will execute until error
@@ -20,7 +19,6 @@ type Instrumentation interface {
type Service interface {
Start(ctx context.Context, reRecord bool) error
- GetContainerIP(ctx context.Context, id uint64) (string, error)
}
type TestDB interface {
diff --git a/pkg/service/record/utils.go b/pkg/service/record/utils.go
index aa96e8bc5c..4240ed3960 100644
--- a/pkg/service/record/utils.go
+++ b/pkg/service/record/utils.go
@@ -1,3 +1,2 @@
-//go:build linux
package record
diff --git a/pkg/service/replay/replay.go b/pkg/service/replay/replay.go
index 37a6308bed..9833fcc208 100644
--- a/pkg/service/replay/replay.go
+++ b/pkg/service/replay/replay.go
@@ -81,7 +81,11 @@ func (r *Replayer) Start(ctx context.Context) error {
g, ctx := errgroup.WithContext(ctx)
ctx = context.WithValue(ctx, models.ErrGroupKey, g)
- var hookCancel context.CancelFunc
+ setupErrGrp, _ := errgroup.WithContext(ctx)
+ setupCtx := context.WithoutCancel(ctx)
+ setupCtx = context.WithValue(ctx, models.ErrGroupKey, setupErrGrp)
+ setupCtx, setupCtxCancel := context.WithCancel(setupCtx)
+
var stopReason = "replay completed successfully"
// defering the stop function to stop keploy in case of any error in record or in case of context cancellation
@@ -92,10 +96,17 @@ func (r *Replayer) Start(ctx context.Context) error {
default:
r.logger.Info("stopping Keploy", zap.String("reason", stopReason))
}
- if hookCancel != nil {
- hookCancel()
+
+ fmt.Printf("SetupCtx?: %v\n", setupCtx.Err())
+ setupCtxCancel()
+ fmt.Printf("SetupCtx?: %v\n", setupCtx.Err())
+ println("setupCtxCancel is cancelled")
+ err := setupErrGrp.Wait()
+ if err != nil {
+ utils.LogError(r.logger, err, "failed to stop setup execution, that covers init container")
}
- err := g.Wait()
+
+ err = g.Wait()
if err != nil {
utils.LogError(r.logger, err, "failed to stop replaying")
}
@@ -172,7 +183,7 @@ func (r *Replayer) Start(ctx context.Context) error {
}
// Instrument will load the hooks and start the proxy
- inst, err := r.Instrument(ctx)
+ inst, err := r.Instrument(setupCtx)
if err != nil {
stopReason = fmt.Sprintf("failed to instrument: %v", err)
utils.LogError(r.logger, err, stopReason)
@@ -182,8 +193,6 @@ func (r *Replayer) Start(ctx context.Context) error {
return fmt.Errorf(stopReason)
}
- hookCancel = inst.HookCancel
-
var testSetResult bool
testRunResult := true
abortTestRun := false
@@ -331,33 +340,19 @@ func (r *Replayer) Instrument(ctx context.Context) (*InstrumentState, error) {
r.logger.Info("Keploy will not mock the outgoing calls when base path is provided", zap.Any("base path", r.config.Test.BasePath))
return &InstrumentState{}, nil
}
- appID, err := r.instrumentation.Setup(ctx, r.config.Command, models.SetupOptions{Container: r.config.ContainerName, DockerNetwork: r.config.NetworkName, DockerDelay: r.config.BuildDelay})
+ // Instrument will setup the environment and start the hooks and proxy
+ appID, err := r.instrumentation.Setup(ctx, r.config.Command, models.SetupOptions{Container: r.config.ContainerName, DockerNetwork: r.config.NetworkName, CommandType: r.config.CommandType, DockerDelay: r.config.BuildDelay, Mode: models.MODE_TEST})
if err != nil {
- if errors.Is(err, context.Canceled) {
- return &InstrumentState{}, err
- }
- return &InstrumentState{}, fmt.Errorf("failed to setup instrumentation: %w", err)
+ stopReason := "failed setting up the environment"
+ utils.LogError(r.logger, err, stopReason)
+ return &InstrumentState{}, fmt.Errorf(stopReason)
}
+
r.config.AppID = appID
var cancel context.CancelFunc
- // starting the hooks and proxy
- select {
- case <-ctx.Done():
- return &InstrumentState{}, context.Canceled
- default:
- hookCtx := context.WithoutCancel(ctx)
- hookCtx, cancel = context.WithCancel(hookCtx)
- err = r.instrumentation.Hook(hookCtx, appID, models.HookOptions{Mode: models.MODE_TEST, EnableTesting: r.config.EnableTesting, Rules: r.config.BypassRules})
- if err != nil {
- cancel()
- if errors.Is(err, context.Canceled) {
- return &InstrumentState{}, err
- }
- return &InstrumentState{}, fmt.Errorf("failed to start the hooks and proxy: %w", err)
- }
- }
- return &InstrumentState{AppID: appID, HookCancel: cancel}, nil
+
+ return &InstrumentState{AppID: 0, HookCancel: cancel}, nil
}
func (r *Replayer) GetNextTestRunID(ctx context.Context) (string, error) {
@@ -833,6 +828,8 @@ func (r *Replayer) RunTestSet(ctx context.Context, testSetID string, testRunID s
if r.config.Test.UpdateTemplate || r.config.Test.BasePath != "" {
removeDoubleQuotes(utils.TemplatizedValues)
// Write the templatized values to the yaml.
+ fmt.Println("POSTScript", conf.PostScript)
+ fmt.Println("PREScript", conf.PreScript)
if len(utils.TemplatizedValues) > 0 {
err = r.testSetConf.Write(ctx, testSetID, &models.TestSet{
PreScript: conf.PreScript,
@@ -876,6 +873,7 @@ func (r *Replayer) SetupOrUpdateMocks(ctx context.Context, appID uint64, testSet
}
if action == Start {
+ // api call here -
err = r.instrumentation.MockOutgoing(ctx, appID, models.OutgoingOptions{
Rules: r.config.BypassRules,
MongoPassword: r.config.Test.MongoPassword,
@@ -889,6 +887,7 @@ func (r *Replayer) SetupOrUpdateMocks(ctx context.Context, appID uint64, testSet
}
}
+ // this will be sent to the proxy
err = r.instrumentation.SetMocks(ctx, appID, filteredMocks, unfilteredMocks)
if err != nil {
utils.LogError(r.logger, err, "failed to set mocks")
@@ -1142,3 +1141,7 @@ func (r *Replayer) DeleteTests(ctx context.Context, testSetID string, testCaseID
func SetTestHooks(testHooks TestHooks) {
HookImpl = testHooks
}
+
+func (r *Replayer) GetContainerIP(ctx context.Context, id uint64) (string, error) {
+ return r.instrumentation.GetContainerIP(ctx, id)
+}
diff --git a/pkg/service/replay/service.go b/pkg/service/replay/service.go
index 5ffa0414b1..02e5dc5dc3 100644
--- a/pkg/service/replay/service.go
+++ b/pkg/service/replay/service.go
@@ -12,7 +12,7 @@ type Instrumentation interface {
//Setup prepares the environment for the recording
Setup(ctx context.Context, cmd string, opts models.SetupOptions) (uint64, error)
//Hook will load hooks and start the proxy server.
- Hook(ctx context.Context, id uint64, opts models.HookOptions) error
+ // Hook(ctx context.Context, id uint64, opts models.HookOptions) error
MockOutgoing(ctx context.Context, id uint64, opts models.OutgoingOptions) error
// SetMocks Allows for setting mocks between test runs for better filtering and matching
SetMocks(ctx context.Context, id uint64, filtered []*models.Mock, unFiltered []*models.Mock) error
@@ -26,9 +26,10 @@ type Instrumentation interface {
type Service interface {
Start(ctx context.Context) error
- Instrument(ctx context.Context) (*InstrumentState, error)
+ // Instrument(ctx context.Context) (*InstrumentState, error)
GetNextTestRunID(ctx context.Context) (string, error)
GetAllTestSetIDs(ctx context.Context) ([]string, error)
+ GetContainerIP(ctx context.Context, id uint64) (string, error)
RunTestSet(ctx context.Context, testSetID string, testRunID string, appID uint64, serveTest bool) (models.TestSetStatus, error)
GetTestSetStatus(ctx context.Context, testRunID string, testSetID string) (models.TestSetStatus, error)
GetTestCases(ctx context.Context, testID string) ([]*models.TestCase, error)
diff --git a/utils/signal_others.go b/utils/signal_others.go
index e450719c76..5b12d349e9 100644
--- a/utils/signal_others.go
+++ b/utils/signal_others.go
@@ -4,6 +4,7 @@ package utils
import (
"context"
+ "fmt"
"os"
"os/exec"
"syscall"
@@ -29,15 +30,15 @@ func SendSignal(logger *zap.Logger, pid int, sig syscall.Signal) error {
func ExecuteCommand(ctx context.Context, logger *zap.Logger, userCmd string, cancel func(cmd *exec.Cmd) func() error, waitDelay time.Duration) CmdError {
// Run the app as the user who invoked sudo
- username := os.Getenv("SUDO_USER")
-
+ // username := os.Getenv("SUDO_USER")
+ fmt.Println("CNTEXT VALUE....", ctx)
cmd := exec.CommandContext(ctx, "sh", "-c", userCmd)
- if username != "" {
- // print all environment variables
- logger.Debug("env inherited from the cmd", zap.Any("env", os.Environ()))
- // Run the command as the user who invoked sudo to preserve the user environment variables and PATH
- cmd = exec.CommandContext(ctx, "sudo", "-E", "-u", os.Getenv("SUDO_USER"), "env", "PATH="+os.Getenv("PATH"), "sh", "-c", userCmd)
- }
+ // if username != "" {
+ // // print all environment variables
+ // logger.Debug("env inherited from the cmd", zap.Any("env", os.Environ()))
+ // // Run the command as the user who invoked sudo to preserve the user environment variables and PATH
+ // cmd = exec.CommandContext(ctx, "sudo", "-E", "-u", os.Getenv("SUDO_USER"), "env", "PATH="+os.Getenv("PATH"), "sh", "-c", userCmd)
+ // }
// Set the cancel function for the command
cmd.Cancel = cancel(cmd)
diff --git a/utils/utils.go b/utils/utils.go
index 04a29ec606..6e9747b4d3 100644
--- a/utils/utils.go
+++ b/utils/utils.go
@@ -5,6 +5,7 @@ import (
"context"
"crypto/sha256"
"debug/elf"
+ "encoding/binary"
"encoding/hex"
"encoding/json"
"errors"
@@ -26,6 +27,7 @@ import (
"golang.org/x/text/language"
"github.com/getsentry/sentry-go"
+ "github.com/google/uuid"
netLib "github.com/shirou/gopsutil/v3/net"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@@ -193,6 +195,16 @@ func DeleteFileIfNotExists(logger *zap.Logger, name string) (err error) {
return nil
}
+func GenerateID() uint64 {
+ // Random AppId uint64 will be generated and maintain in a map and return the id to client
+ newUUID := uuid.New()
+
+ // app id will be sent by the client.
+ // Convert the first 8 bytes of the UUID to an int64
+ id := int64(binary.BigEndian.Uint64(newUUID[:8]))
+ return uint64(id)
+}
+
type GitHubRelease struct {
TagName string `json:"tag_name"`
Body string `json:"body"`