From 7f838e70586184d987d80a82f5fe39476892188f Mon Sep 17 00:00:00 2001 From: Mauro Ezequiel Moltrasio Date: Fri, 12 Apr 2024 07:19:45 -0400 Subject: [PATCH] Add an introspection endpoint for container metadata (#1614) * Add k8s based tests for namespace metadata (#1615) --- .github/workflows/k8s-integration-tests.yml | 99 +++++++ .github/workflows/main.yml | 13 + ansible/README.md | 117 ++++++++ ansible/k8s-integration-tests.yml | 261 +++++++++++++++++ ansible/requirements.txt | 1 + ansible/requirements.yml | 1 + collector/lib/CollectorConfig.cpp | 3 + collector/lib/CollectorConfig.h | 2 + collector/lib/CollectorService.cpp | 9 + collector/lib/ContainerInfoInspector.cpp | 34 +++ collector/lib/ContainerInfoInspector.h | 34 +++ collector/lib/IntrospectionEndpoint.cpp | 33 +++ collector/lib/IntrospectionEndpoint.h | 27 ++ collector/lib/system-inspector/Service.cpp | 3 + collector/lib/system-inspector/Service.h | 4 + docs/troubleshooting.md | 90 ++++++ integration-tests/Dockerfile | 14 +- integration-tests/Makefile | 2 +- integration-tests/go.mod | 29 +- integration-tests/go.sum | 133 ++++++++- integration-tests/integration_test.go | 4 +- integration-tests/k8s_test.go | 19 ++ integration-tests/pkg/collector/collector.go | 45 +++ .../collector/collector_docker.go} | 91 +++--- .../pkg/collector/collector_k8s.go | 215 ++++++++++++++ .../{suites => pkg}/common/utils.go | 0 .../{suites => pkg}/config/config.go | 0 .../{suites => pkg}/config/env.go | 0 .../{suites => pkg}/config/images.go | 0 integration-tests/pkg/executor/executor.go | 40 +++ .../executor/executor_docker.go} | 91 +++--- .../pkg/executor/executor_k8s.go | 174 ++++++++++++ .../{suites/common => pkg/executor}/retry.go | 2 +- .../mock_sensor/expect_conn.go | 2 +- .../mock_sensor/expect_proc.go | 2 +- .../{suites => pkg}/mock_sensor/ring.go | 0 .../{suites => pkg}/mock_sensor/server.go | 4 +- .../{suites => pkg}/types/endpoint.go | 0 .../{suites => pkg}/types/listen_address.go | 0 .../{suites => pkg}/types/network.go | 0 .../{suites => pkg}/types/process.go | 0 .../types/process_originator.go | 0 integration-tests/suites/async_connections.go | 7 +- integration-tests/suites/base.go | 74 +++-- integration-tests/suites/benchmark.go | 4 +- .../suites/connections_and_endpoints.go | 9 +- .../suites/duplicate_endpoints.go | 7 +- integration-tests/suites/gperftools.go | 2 +- integration-tests/suites/image_json.go | 2 +- integration-tests/suites/k8s_namespace.go | 266 ++++++++++++++++++ integration-tests/suites/listening_ports.go | 9 +- .../suites/missing_proc_scrape.go | 4 +- integration-tests/suites/perf_event_open.go | 2 +- integration-tests/suites/process_network.go | 6 +- .../suites/processes_and_endpoints.go | 9 +- integration-tests/suites/procfs_scraper.go | 9 +- .../suites/repeated_network_flow.go | 6 +- integration-tests/suites/socat.go | 9 +- integration-tests/suites/symlink_process.go | 7 +- 59 files changed, 1853 insertions(+), 177 deletions(-) create mode 100644 .github/workflows/k8s-integration-tests.yml create mode 100644 ansible/k8s-integration-tests.yml create mode 100644 collector/lib/ContainerInfoInspector.cpp create mode 100644 collector/lib/ContainerInfoInspector.h create mode 100644 collector/lib/IntrospectionEndpoint.cpp create mode 100644 collector/lib/IntrospectionEndpoint.h create mode 100644 integration-tests/k8s_test.go create mode 100644 integration-tests/pkg/collector/collector.go rename integration-tests/{suites/common/collector_manager.go => pkg/collector/collector_docker.go} (63%) create mode 100644 integration-tests/pkg/collector/collector_k8s.go rename integration-tests/{suites => pkg}/common/utils.go (100%) rename integration-tests/{suites => pkg}/config/config.go (100%) rename integration-tests/{suites => pkg}/config/env.go (100%) rename integration-tests/{suites => pkg}/config/images.go (100%) create mode 100644 integration-tests/pkg/executor/executor.go rename integration-tests/{suites/common/executor.go => pkg/executor/executor_docker.go} (71%) create mode 100644 integration-tests/pkg/executor/executor_k8s.go rename integration-tests/{suites/common => pkg/executor}/retry.go (97%) rename integration-tests/{suites => pkg}/mock_sensor/expect_conn.go (98%) rename integration-tests/{suites => pkg}/mock_sensor/expect_proc.go (98%) rename integration-tests/{suites => pkg}/mock_sensor/ring.go (100%) rename integration-tests/{suites => pkg}/mock_sensor/server.go (99%) rename integration-tests/{suites => pkg}/types/endpoint.go (100%) rename integration-tests/{suites => pkg}/types/listen_address.go (100%) rename integration-tests/{suites => pkg}/types/network.go (100%) rename integration-tests/{suites => pkg}/types/process.go (100%) rename integration-tests/{suites => pkg}/types/process_originator.go (100%) create mode 100644 integration-tests/suites/k8s_namespace.go diff --git a/.github/workflows/k8s-integration-tests.yml b/.github/workflows/k8s-integration-tests.yml new file mode 100644 index 0000000000..8b3c75f5d0 --- /dev/null +++ b/.github/workflows/k8s-integration-tests.yml @@ -0,0 +1,99 @@ +name: K8S based integration tests + +on: + workflow_call: + inputs: + collector-tag: + description: | + Tag used for running the integration tests + type: string + required: true + collector-qa-tag: + description: | + Tag used for QA containers + type: string + required: true + +env: + ANSIBLE_CONFIG: ${{ github.workspace }}/ansible/ansible.cfg + COLLECTOR_TESTS_IMAGE: quay.io/rhacs-eng/qa-multi-arch:collector-tests-${{ inputs.collector-tag }} + +jobs: + k8s-integration-tests: + name: Run k8s integration tests + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Install dependencies + run: | + python -m pip install -r "${{ github.workspace }}/ansible/requirements.txt" + ansible-galaxy collection install -r "${{ github.workspace }}/ansible/requirements.yml" + + - name: Create inventory.yml + run: | + cat << EOF > /tmp/inventory.yml + all: + hosts: + localhost: + ansible_connection: local + ansible_python_interpreter: "{{ansible_playbook_python}}" + EOF + + - name: Create ansible variables file + run: | + cat << EOF > /tmp/vars.yml + --- + tester_image: ${{ env.COLLECTOR_TESTS_IMAGE }} + collector_image: quay.io/stackrox-io/collector:${{ inputs.collector-tag }} + collector_root: ${{ github.workspace }} + EOF + + - name: Login to quay.io/rhacs-eng + uses: docker/login-action@v3 + with: + registry: quay.io + username: ${{ secrets.QUAY_RHACS_ENG_RW_USERNAME }} + password: ${{ secrets.QUAY_RHACS_ENG_RW_PASSWORD }} + + - name: Pull tests image + run: | + docker pull ${{ env.COLLECTOR_TESTS_IMAGE }} + + - name: Run tests + run: | + ansible-playbook \ + -i /tmp/inventory.yml \ + -e @/tmp/vars.yml \ + ansible/k8s-integration-tests.yml + + - name: Store artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: k8s-logs + path: | + ${{ github.workspace }}/integration-tests/container-logs/**/* + + notify: + runs-on: ubuntu-latest + if: always() && contains(join(needs.*.result, ','), 'failure') && github.event_name == 'push' + needs: + - k8s-integration-tests + steps: + - name: Slack notification + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_WEBHOOK: ${{ secrets.SLACK_COLLECTOR_ONCALL_WEBHOOK }} + SLACK_CHANNEL: team-acs-collector-oncall + SLACK_COLOR: failure + SLACK_LINK_NAMES: true + SLACK_TITLE: "K8S Integration tests failed." + MSG_MINIMAL: actions url,commit + SLACK_MESSAGE: | + @acs-collector-oncall diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4ef1139c7f..911856639d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -140,6 +140,19 @@ jobs: - build-test-containers secrets: inherit + k8s-integration-tests: + uses: ./.github/workflows/k8s-integration-tests.yml + with: + collector-tag: ${{ needs.init.outputs.collector-tag }} + collector-qa-tag: ${{ needs.init.outputs.collector-qa-tag }} + if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip-integration-tests') }} + needs: + - init + - build-collector-slim + - build-collector-full + - build-test-containers + secrets: inherit + benchmarks: uses: ./.github/workflows/benchmarks.yml with: diff --git a/ansible/README.md b/ansible/README.md index 3a09d0a8c2..e9e2127c66 100644 --- a/ansible/README.md +++ b/ansible/README.md @@ -275,3 +275,120 @@ To find VM definitions see [group_vars/all.yml](./group_vars/all.yml) [^1]: there should be no CI related functionality within this directory outside the `ci/` inventory. + +### K8S based integration tests +There are a set of tests that can be run on k8s clusters. In order to make +these as easy as possible to execute, there is an ansible playbook at +`$REPO_ROOT/ansible/k8s-integration-tests.yml`. This playbook runs solely on +ansible variables and attempts to be as flexible and as easy to use as +possible, so they also allow the tests to run on a throw away KinD cluster that +is created and deleted by the playbook, or on an existing cluster of your own. + +The following section will describe how to configure and run these +tests. + +#### Inventory file +As with every ansible playbook, an inventory file is required. The k8s tests +are prepared to use the local kubeconfig, so if you want to run the tests on an +existing cluster or by using a throw away one with KinD, the following +inventory should be enough: + +```yaml +all: + hosts: + local: + ansible_connection: local + ansible_python_interpreter: "{{ansible_playbook_python}}" +``` + +For other configurations that require access to remote systems, refer to +[ansible's guide on managing inventories](https://docs.ansible.com/ansible/latest/inventory_guide/intro_inventory.html). + +#### Variables +The following is a list of variables used by the playbook. + +| Variable Name | Description | +| --- | --- | +| tester_image | The image to run the tests, created from the `build-image` integration-tests make target. | +| collector_image | The collector image to be tested. | +| collector_root | The path to the root of the collector repo, used for dumping logs. | +| cluster_name | When using a KinD cluster, the name to be used for the cluster where the tests will run. Default: collector-tests | + +Easiest way to manage these variables is to create a yaml file that can later +be supplied to the `ansible-playbook` command, the following should work as a +template for such a file + +```yaml +--- +tester_image: +collector_image: +collector_root: +cluster_name: collector-tests +``` + +#### Tags +The playbook also has a some tags that allow for more flexibility in execution, +keep in mind that if the `--tags` argument is not supplied to +`ansible-playbook` it will run all steps in the playbook. + +| Tag name | Description | +| --- | --- | +| test-only | Run the tests and clean up the environment, skip all steps handling disposable KinD clusters. | +| cleanup | Run the cleanup steps only, removing k8s objects such as namespaces, cluster roles, etc... | + +#### Examples +##### Run all tests on a throw-away KinD cluster + +Assuming your inventory is in `inventory.yml` and variables are set in a +`k8s-tests.yml` file, the following command will create a KinD cluster, run +all integration tests and teardown the cluster. + +```sh +ansible-playbook \ + -i inventory.yml \ + -e '@k8s-tests.yml' \ + k8s-integration-tests.yml +``` + +##### Run the tests on an existing cluster + +The following command will only run the integration tests, once the playbook +is done executing the cluster will be left in the same state it was before +running it. + +```sh +ansible-playbook \ + -i inventory.yml \ + -e '@k8s-tests.yml' \ + --tags test-only \ + k8s-integration-tests.yml +``` + +##### Run the cleanup steps on a cluster + +In case something goes wrong and you want to remove all objects created by the +playbook from your cluster, the `cleanup` tag can be used. + +```sh +ansible-playbook \ + -i inventory.yml \ + -e '@k8s-tests.yml' \ + --tags cleanup \ + k8s-integration-tests.yml +``` + +##### Running the playbook step-by-step + +This is more of a general ansible-playbook tip, but still useful for debugging. +Using the `--step` argument will cause the ansible-playbook command to stop at +every step and wait for user confirmation. + +```sh +ansible-playbook \ + -i inventory.yml \ + -e '@k8s-tests.yml' \ + --step \ + k8s-integration-tests.yml +``` +For more tips on troubleshooting ansible playbooks see +[Executing playbooks for troubleshooting](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_startnstep.html). diff --git a/ansible/k8s-integration-tests.yml b/ansible/k8s-integration-tests.yml new file mode 100644 index 0000000000..58870ff432 --- /dev/null +++ b/ansible/k8s-integration-tests.yml @@ -0,0 +1,261 @@ +--- +- name: Run k8s based integration tests on KinD + hosts: all + + vars: + kind_name: "{{ cluster_name | default('collector-tests') }}" + + tasks: + - name: Check KinD is installed + ansible.builtin.command: which kind + register: is_kind_available + ignore_errors: true + changed_when: false + + - name: Fail when kind is unavailable + ansible.builtin.fail: + msg: kind not found. https://kind.sigs.k8s.io/docs/user/quick-start/#installation + when: is_kind_available.rc != 0 + + - name: Create a test cluster + ansible.builtin.shell: + cmd: | + cat << EOF | kind --name {{ kind_name }} --config=- create cluster + kind: Cluster + apiVersion: kind.x-k8s.io/v1alpha4 + nodes: + - role: control-plane + extraMounts: + - hostPath: {{ collector_root }} + containerPath: /tmp/collector + EOF + + changed_when: false + + - name: Check collector image exists locally + community.docker.docker_image_info: + name: "{{ collector_image }}" + register: collector_image_info + + - name: Load collector image into kind cluster + ansible.builtin.command: + cmd: kind --name {{ kind_name }} load docker-image {{ collector_image }} + when: collector_image_info.images | length == 1 + changed_when: false + + - name: Check tester image exists locally + community.docker.docker_image_info: + name: "{{ tester_image }}" + register: tester_image_info + + - name: Load tester image into kind cluster + ansible.builtin.command: + cmd: kind --name {{ kind_name }} load docker-image {{ tester_image }} + when: tester_image_info.images | length == 1 + changed_when: false + + - name: Create a namespace for collector tests + kubernetes.core.k8s: + state: present + definition: + apiVersion: v1 + kind: Namespace + metadata: + name: collector-tests + tags: + - test-only + + - name: Create custom RBAC permissions for tester + kubernetes.core.k8s: + state: present + definition: + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + name: tester-permissions + rules: + - apiGroups: + - '*' + resources: + - '*' + verbs: + - '*' + tags: + - test-only + + - name: Modify permissions to default service account for tester + kubernetes.core.k8s: + state: present + definition: + kind: ClusterRoleBinding + metadata: + name: tester-permissions-binding + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: tester-permissions + subjects: + - kind: ServiceAccount + name: default + namespace: collector-tests + tags: + - test-only + + - name: Create a service for the tester pod + kubernetes.core.k8s: + state: present + definition: + apiVersion: v1 + kind: Service + metadata: + name: tester-svc + namespace: collector-tests + spec: + selector: + app: tester + ports: + - protocol: TCP + port: 9999 + targetPort: 9999 + tags: + - test-only + + - name: Wait for default service account to be available + kubernetes.core.k8s_info: + kind: ServiceAccount + name: default + namespace: collector-tests + register: account_status + until: account_status.resources | length == 1 + retries: 10 + delay: 5 + tags: + - test-only + + - name: Run k8s tests + kubernetes.core.k8s: + state: present + wait: true + definition: + apiVersion: v1 + kind: Pod + metadata: + name: tester + namespace: collector-tests + labels: + app: tester + spec: + restartPolicy: Never + containers: + - name: tester + image: "{{ tester_image }}" + env: + - name: REMOTE_HOST_TYPE + value: k8s + - name: COLLECTOR_QA_TAG + value: "{{ lookup('env', 'COLLECTOR_QA_TAG', default=lookup('file', collector_root + '/integration-tests/container/QA_TAG')) }}" + - name: COLLECTOR_IMAGE + value: "{{ collector_image }}" + args: ["-test.run", "^TestK8s.*"] + ports: + - containerPort: 9999 + volumeMounts: + - mountPath: /tests/container-logs + name: logs + securityContext: + privileged: true + volumes: + - name: logs + hostPath: + path: /tmp/collector/integration-tests/container-logs + tags: + - test-only + + - name: Wait until tests are done + kubernetes.core.k8s_info: + kind: Pod + name: tester + namespace: collector-tests + register: tests_status + until: tests_status.resources[0].status.phase != "Running" + retries: 100 + delay: 6 + ignore_errors: true + tags: + - test-only + + - name: Get tester logs + kubernetes.core.k8s_log: + kind: Pod + name: tester + namespace: collector-tests + register: log + tags: + - test-only + + - name: Output tester logs + ansible.builtin.debug: + msg: "{{ log.log_lines }}" + tags: + - test-only + + - name: Cleanup namespace + kubernetes.core.k8s: + state: absent + definition: + apiVersion: v1 + kind: Namespace + metadata: + name: collector-tests + tags: + - test-only + - cleanup + + - name: Cleanup ClusterRole + kubernetes.core.k8s: + state: absent + definition: + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + name: tester-permissions + rules: + - apiGroups: + - '*' + resources: + - '*' + verbs: + - '*' + tags: + - test-only + - cleanup + + - name: Cleanup ClusterRoleBinding + kubernetes.core.k8s: + state: absent + definition: + kind: ClusterRoleBinding + metadata: + name: tester-permissions-binding + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: tester-permissions + subjects: + - kind: ServiceAccount + name: default + namespace: collector-tests + tags: + - test-only + - cleanup + + - name: Teardown test cluster + ansible.builtin.command: + cmd: kind --name {{ kind_name }} delete cluster + when: true + changed_when: false + + - name: Tests failed + ansible.builtin.fail: + msg: "Tests have failed!!" + when: tests_status.resources[0].status.phase == "Failed" diff --git a/ansible/requirements.txt b/ansible/requirements.txt index 4beeee6fe9..78559afd01 100644 --- a/ansible/requirements.txt +++ b/ansible/requirements.txt @@ -2,3 +2,4 @@ ansible requests google-auth selinux +kubernetes diff --git a/ansible/requirements.yml b/ansible/requirements.yml index 67b8ceb7f4..c8fd453bdd 100644 --- a/ansible/requirements.yml +++ b/ansible/requirements.yml @@ -3,3 +3,4 @@ collections: - community.general - community.docker - ibm.cloudcollection +- kubernetes.core diff --git a/collector/lib/CollectorConfig.cpp b/collector/lib/CollectorConfig.cpp index 4d7ccb701f..825efecc98 100644 --- a/collector/lib/CollectorConfig.cpp +++ b/collector/lib/CollectorConfig.cpp @@ -51,6 +51,8 @@ BoolEnvVar enable_runtime_filters("ROX_COLLECTOR_RUNTIME_FILTERS_ENABLED", false BoolEnvVar use_docker_ce("ROX_COLLECTOR_CE_USE_DOCKER", false); BoolEnvVar use_podman_ce("ROX_COLLECTOR_CE_USE_PODMAN", false); +BoolEnvVar enable_introspection("ROX_COLLECTOR_INTROSPECTION_ENABLE", false); + } // namespace constexpr bool CollectorConfig::kTurnOffScrape; @@ -79,6 +81,7 @@ void CollectorConfig::InitCollectorConfig(CollectorArgs* args) { enable_runtime_filters_ = enable_runtime_filters.value(); use_docker_ce_ = use_docker_ce.value(); use_podman_ce_ = use_podman_ce.value(); + enable_introspection_ = enable_introspection.value(); for (const auto& syscall : kSyscalls) { syscalls_.push_back(syscall); diff --git a/collector/lib/CollectorConfig.h b/collector/lib/CollectorConfig.h index 1a177ebc10..5000c7aab3 100644 --- a/collector/lib/CollectorConfig.h +++ b/collector/lib/CollectorConfig.h @@ -78,6 +78,7 @@ class CollectorConfig { bool EnableRuntimeFilters() const { return enable_runtime_filters_; } bool UseDockerCe() const { return use_docker_ce_; } bool UsePodmanCe() const { return use_podman_ce_; } + bool IsIntrospectionEnabled() const { return enable_introspection_; } const std::vector& GetConnectionStatsQuantiles() const { return connection_stats_quantiles_; } double GetConnectionStatsError() const { return connection_stats_error_; } unsigned int GetConnectionStatsWindow() const { return connection_stats_window_; } @@ -113,6 +114,7 @@ class CollectorConfig { bool enable_runtime_filters_; bool use_docker_ce_; bool use_podman_ce_; + bool enable_introspection_; std::vector connection_stats_quantiles_; double connection_stats_error_; unsigned int connection_stats_window_; diff --git a/collector/lib/CollectorService.cpp b/collector/lib/CollectorService.cpp index 855c57a19e..1e1e68b16b 100644 --- a/collector/lib/CollectorService.cpp +++ b/collector/lib/CollectorService.cpp @@ -1,5 +1,7 @@ #include "CollectorService.h" +#include "ContainerInfoInspector.h" + extern "C" { #include } @@ -58,6 +60,8 @@ void CollectorService::RunForever() { std::unique_ptr net_status_notifier; + std::unique_ptr container_info_inspector; + CLOG(INFO) << "Network scrape interval set to " << config_.ScrapeInterval() << " seconds"; if (config_.grpc_channel) { @@ -99,6 +103,11 @@ void CollectorService::RunForever() { CLOG(FATAL) << "Unable to start collector stats exporter"; } + if (config_.IsIntrospectionEnabled()) { + container_info_inspector = std::make_unique(system_inspector_.GetContainerMetadataInspector()); + server.addHandler(container_info_inspector->kBaseRoute, container_info_inspector.get()); + } + system_inspector_.Init(config_, conn_tracker); system_inspector_.Start(); diff --git a/collector/lib/ContainerInfoInspector.cpp b/collector/lib/ContainerInfoInspector.cpp new file mode 100644 index 0000000000..25316e677c --- /dev/null +++ b/collector/lib/ContainerInfoInspector.cpp @@ -0,0 +1,34 @@ +#include "ContainerInfoInspector.h" + +#include +#include +#include + +namespace collector { +const char* const ContainerInfoInspector::kBaseRoute = "/state/containers/"; + +bool ContainerInfoInspector::handleGet(CivetServer* server, struct mg_connection* conn) { + const mg_request_info* req_info = mg_get_request_info(conn); + if (req_info == nullptr) { + return ServerError(conn, "unable to read request"); + } + + std::string_view url = req_info->local_uri; + std::string container_id(url.substr(url.rfind('/') + 1)); + + if (container_id.length() != 12) { + return ClientError(conn, "invalid container ID"); + } + + Json::Value root; + + root["container_id"] = container_id; + root["namespace"] = std::string(container_metadata_inspector_->GetNamespace(container_id)); + + mg_printf(conn, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nConnection: close\r\n\r\n"); + mg_printf(conn, "%s\r\n", writer_.write(root).c_str()); + + return true; +} + +} // namespace collector diff --git a/collector/lib/ContainerInfoInspector.h b/collector/lib/ContainerInfoInspector.h new file mode 100644 index 0000000000..43795b4639 --- /dev/null +++ b/collector/lib/ContainerInfoInspector.h @@ -0,0 +1,34 @@ +#ifndef _CONTAINER_INFO_INSPECTOR_ +#define _CONTAINER_INFO_INSPECTOR_ + +#include +#include +#include +#include +#include + +#include "ContainerMetadata.h" +#include "IntrospectionEndpoint.h" +#include "json/writer.h" + +namespace collector { + +using QueryParams = std::unordered_map; + +class ContainerInfoInspector : public IntrospectionEndpoint { + public: + static const char* const kBaseRoute; + + ContainerInfoInspector(const std::shared_ptr& cmi) : container_metadata_inspector_(cmi) {} + + // implementation of CivetHandler + bool handleGet(CivetServer* server, struct mg_connection* conn) override; + + private: + std::shared_ptr container_metadata_inspector_; + Json::FastWriter writer_; +}; + +} // namespace collector + +#endif //_CONTAINER_INFO_INSPECTOR_ diff --git a/collector/lib/IntrospectionEndpoint.cpp b/collector/lib/IntrospectionEndpoint.cpp new file mode 100644 index 0000000000..0630a38aaf --- /dev/null +++ b/collector/lib/IntrospectionEndpoint.cpp @@ -0,0 +1,33 @@ +#include "IntrospectionEndpoint.h" + +#include + +namespace collector { + +QueryParams IntrospectionEndpoint::ParseParameters(const char* queryString) { + QueryParams params; + + if (queryString == nullptr) { + return params; + } + + std::stringstream query_stringstream(queryString); + while (query_stringstream.good()) { + std::string statement; + + std::getline(query_stringstream, statement, '&'); + + size_t equal = statement.find('='); + + if (equal != std::string::npos) { + params[statement.substr(0, equal)] = statement.substr(equal + 1); + } + } + return params; +} + +std::optional IntrospectionEndpoint::GetParameter(const QueryParams& params, const std::string& paramName) { + return params.count(paramName) != 0 ? std::make_optional(params.at(paramName)) : std::nullopt; +} + +} // namespace collector diff --git a/collector/lib/IntrospectionEndpoint.h b/collector/lib/IntrospectionEndpoint.h new file mode 100644 index 0000000000..17b41f2690 --- /dev/null +++ b/collector/lib/IntrospectionEndpoint.h @@ -0,0 +1,27 @@ +#ifndef _INTROSPECTION_ENDPOINT_ +#define _INTROSPECTION_ENDPOINT_ + +#include +#include +#include + +namespace collector { + +using QueryParams = std::unordered_map; + +class IntrospectionEndpoint : public CivetHandler { + protected: + static QueryParams ParseParameters(const char* queryString); + static std::optional GetParameter(const QueryParams& params, const std::string& paramName); + + static bool ServerError(struct mg_connection* conn, const char* err) { + return mg_send_http_error(conn, 500, err) >= 0; + } + static bool ClientError(struct mg_connection* conn, const char* err) { + return mg_send_http_error(conn, 400, err) >= 0; + } +}; + +} // namespace collector + +#endif // _INTROSPECTION_ENDPOINT_ diff --git a/collector/lib/system-inspector/Service.cpp b/collector/lib/system-inspector/Service.cpp index f753b8e7c0..9988deba8b 100644 --- a/collector/lib/system-inspector/Service.cpp +++ b/collector/lib/system-inspector/Service.cpp @@ -14,6 +14,7 @@ #include "CollectorException.h" #include "CollectorStats.h" #include "ContainerEngine.h" +#include "ContainerMetadata.h" #include "EventNames.h" #include "HostInfo.h" #include "KernelDriver.h" @@ -89,6 +90,8 @@ bool Service::InitKernel(const CollectorConfig& config, const DriverCandidate& c inspector_->get_parser()->set_track_connection_status(true); } + container_metadata_inspector_.reset(new ContainerMetadata(inspector_.get())); + if (config.EnableRuntimeFilters()) { uint64_t mask = 1 << CT_CRI | 1 << CT_CRIO | diff --git a/collector/lib/system-inspector/Service.h b/collector/lib/system-inspector/Service.h index 3cb043166f..9100045d97 100644 --- a/collector/lib/system-inspector/Service.h +++ b/collector/lib/system-inspector/Service.h @@ -11,6 +11,7 @@ #include "libsinsp/sinsp.h" +#include "ContainerMetadata.h" #include "Control.h" #include "DriverCandidates.h" #include "SignalHandler.h" @@ -44,6 +45,8 @@ class Service : public SystemInspector { void GetProcessInformation(uint64_t pid, ProcessInfoCallbackRef callback); + std::shared_ptr GetContainerMetadataInspector() { return container_metadata_inspector_; }; + private: FRIEND_TEST(SystemInspectorServiceTest, FilterEvent); @@ -69,6 +72,7 @@ class Service : public SystemInspector { mutable std::mutex libsinsp_mutex_; std::unique_ptr inspector_; + std::shared_ptr container_metadata_inspector_; std::unique_ptr default_formatter_; std::unique_ptr signal_client_; std::vector signal_handlers_; diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index bce68215c0..e76e3ae7f1 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -450,3 +450,93 @@ $ curl collector:8080/profile/heap The resulting profile could be processed with `pprof` to get a human-readable output with debugging symbols. + +## Introspection endpoints + +Another method for troubleshooting collector during development cycles is to +use its introspection endpoints. These are REST like endpoints exposed on port +`8080` and provide some more insights into data being held by collector in +JSON format. + +In order to enable these introspection endpoints, the +`ROX_COLLECTOR_INTROSPECTION_ENABLE` environment variable needs to be set to +`true`. + +The endpoints should be reachable from within the k8s cluster the collector +daemonset is deployed, but you can also access it from your local host by +using port-forward: + +```sh +$ kubectl -n stackrox port-forward ds/collector 8080:8080 & +[1] 64384 +Forwarding from 127.0.0.1:8080 -> 8080 +Forwarding from [::1]:8080 -> 8080 +``` + +### Container metadata endpoint + +This endpoint provides a way for users to query metadata of a given container +by its ID, querying the `/state/containers/{containerID}` endpoint. The +containerID argument needs to be provided in its short form (first 12 +characters). + +```sh +$ curl collector:8080/state/containers/01e8c0454972 +``` + +In order for the metadata to be collected, the collector daemonset needs to be +edited for it to have access to the corresponding CRI socket on the system. +Since metadata collection is locked behind a feature flag, the +`ROX_COLLECTOR_RUNTIME_FILTERS_ENABLED` needs to be set to `true` as well. + +```yaml +spec: + template: + spec: + containers: + ... + env: + - name: ROX_COLLECTOR_RUNTIME_FILTERS_ENABLED + value: "true" + ... + volumeMounts: + ... + - mountPath: /host/run/containerd/containerd.sock + mountPropagation: HostToContainer + name: containerd-sock + - mountPath: /host/run/crio/crio.sock + mountPropagation: HostToContainer + name: crio-sock + ... + volumes: + ... + - hostPath: + path: /run/containerd/containerd.sock + name: containerd-sock + - hostPath: + path: /run/crio/crio.sock + name: crio-sock +``` + +Once edited, you can use the following command to extract the container IDs +from a given kubernetes object. + +```sh +$ kubectl -n stackrox get pods -l "app=collector" -o json \ + | jq -r '.items[].status.containerStatuses[].containerID' \ + | sed -e 's#containerd://##' \ + | cut -c -12 +01e8c0454972 +80bfaff4d03b +``` + +You can then port-forward the collector daemonset to be able to query the +container metadata endpoint from your localhost. Alternatively, you can exec +into a pod with access to an HTTP tool in order to query the endpoint from +within the cluster itself. + +```sh +$ curl "localhost:8080/state/containers/01e8c0454972" +Handling connection for 8080 +{"container_id":"01e8c0454972","namespace":"stackrox"} +``` diff --git a/integration-tests/Dockerfile b/integration-tests/Dockerfile index 000f4171c0..9633b2759b 100644 --- a/integration-tests/Dockerfile +++ b/integration-tests/Dockerfile @@ -4,16 +4,22 @@ FROM golang:1.19 as builder ARG TEST_ROOT +ENV GOCACHE=/root/.cache/go-build + RUN mkdir -p "$TEST_ROOT" +WORKDIR "$TEST_ROOT" + +# Cache dependencies +COPY go.* "$TEST_ROOT" +RUN go mod download COPY suites "$TEST_ROOT/suites/" +COPY pkg "$TEST_ROOT/pkg/" COPY integration_test.go "$TEST_ROOT" COPY benchmark_test.go "$TEST_ROOT" -COPY go.* "$TEST_ROOT" - -WORKDIR "$TEST_ROOT" +COPY k8s_test.go "$TEST_ROOT" -RUN CGO_ENABLED=0 GOOS=linux GOARCH=$TARGETARCH go test -tags bench -c -o collector-tests +RUN --mount=type=cache,target="/root/.cache/go-build" CGO_ENABLED=0 GOOS=linux GOARCH=$TARGETARCH go test -tags bench,k8s -c -o collector-tests FROM alpine:3.18 diff --git a/integration-tests/Makefile b/integration-tests/Makefile index 9980b1ed7c..fa057204aa 100644 --- a/integration-tests/Makefile +++ b/integration-tests/Makefile @@ -46,7 +46,7 @@ $(foreach element,$(ALL_TESTS),$(eval $(call make-test-target,$(element)))) .PHONY: build build: mkdir -p bin - go test -tags bench -c -o bin/collector-tests + go test -tags bench,k8s -c -o bin/collector-tests .PHONY: build-image build-image: diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 814af8ec14..65d5f78345 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -14,12 +14,19 @@ require ( golang.org/x/sys v0.13.0 google.golang.org/grpc v1.58.3 gopkg.in/yaml.v3 v3.0.1 + k8s.io/api v0.26.9 + k8s.io/apimachinery v0.27.4 + k8s.io/client-go v0.26.9 k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/emicklei/go-restful/v3 v3.10.1 // indirect github.com/go-logr/logr v1.2.4 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.3 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect @@ -29,23 +36,43 @@ require ( github.com/gonum/internal v0.0.0-20181124074243-f884aa714029 // indirect github.com/gonum/lapack v0.0.0-20181123203213-e4cdc5a0bff9 // indirect github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9 // indirect + github.com/google/gnostic v0.6.9 // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.3.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.6 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/moby/spdystream v0.2.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stackrox/scanner v0.0.0-20230411230651-f2265de65ce4 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.10.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/net v0.17.0 // indirect + golang.org/x/oauth2 v0.11.0 // indirect + golang.org/x/term v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect + golang.org/x/time v0.3.0 // indirect + google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230807174057-1744710a1577 // indirect google.golang.org/protobuf v1.31.0 // indirect - k8s.io/apimachinery v0.27.4 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/klog/v2 v2.100.1 // indirect + k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect ) replace ( diff --git a/integration-tests/go.sum b/integration-tests/go.sum index 842eaac7d4..8551657697 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -2,32 +2,62 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/connorgorman/protobuf v1.2.2-0.20210115205927-b892c1b298f7 h1:YsgEuC8PhmdxkjGb3/l4inKcwVKuPXp1YELVPZkIByA= github.com/connorgorman/protobuf v1.2.2-0.20210115205927-b892c1b298f7/go.mod h1:4n/qquk+A505mqkK9+o7Xth9+UxUWFc4/5faDXkYyyU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= +github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac h1:Q0Jsdxl5jbxouNs1TQYt0gxesYMU4VXRbsTlgDloZ50= @@ -44,14 +74,27 @@ github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9 h1:V2IgdyerlBa/MxaEFR github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9/go.mod h1:0EXg4mc1CNP0HCqCz+K4ts155PXIlUywf0wqN+GfPZw= github.com/gonum/stat v0.0.0-20181125101827-41a0da705a5b h1:fbskpz/cPqWH8VqkQ7LJghFkl2KPAiIFUHrTJ2O3RGk= github.com/gonum/stat v0.0.0-20181125101827-41a0da705a5b/go.mod h1:Z4GIJBJO3Wa4gD4vbwQxXXZ+WHmW6E9ixmNrwvs0iZs= +github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= +github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -61,14 +104,34 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/golang-lru/v2 v2.0.6 h1:3xi/Cafd1NaoEnS/yDssIiuVeDVywU0QdFGl3aQaQHM= github.com/hashicorp/golang-lru/v2 v2.0.6/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -78,20 +141,33 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace h1:9PNP1jnUjRhfmGMlkXHjYPishpcw4jpSt/V/xYY3FMA= github.com/stackrox/scanner v0.0.0-20230411230651-f2265de65ce4 h1:GfGtz9MCBj9L36d7KGaV6HCEoQY+PAy2fXWvozK0GLs= github.com/stackrox/scanner v0.0.0-20230411230651-f2265de65ce4/go.mod h1:4SRyOkdm9xp3Bca85Hp3636r7FvnA610Laxn3nbQBzc= github.com/stackrox/stackrox v0.0.0-20230918143419-23e948778ebe h1:BH8u0CrunvEbd8v8VMiv6oACyX6QbfEHR3xD3wHv/Iw= github.com/stackrox/stackrox v0.0.0-20230918143419-23e948778ebe/go.mod h1:4sb7tNBk1O37Y3QVnt5QGc+dWtsMHVsONUqZ4LSsfe4= github.com/stackrox/zap v1.15.1-0.20200720133746-810fd602fd0f h1:Ofa3PAa609eSHcHP2kCJDUWUnuEWfiqXhsuppk/QtOE= github.com/stackrox/zap v1.15.1-0.20200720133746-810fd602fd0f/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/thoas/go-funk v0.9.3 h1:7+nAEx3kn5ZJcnDm2Bh23N2yOtweO14bi//dvRtgLpw= github.com/thoas/go-funk v0.9.3/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= @@ -119,12 +195,17 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= +golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -133,11 +214,25 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -151,9 +246,13 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 h1:L6iMMGrtzgHsWofoFcihmDEMYeDR9KN/ThbPWGrh++g= google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5/go.mod h1:oH/ZOT02u4kWEp7oYBGYFFkCdKS/uYR9Z7+0/xuuFp8= google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5 h1:nIgk/EEq3/YlnmVVXVnm14rC2oxgs1o0ong4sD/rd44= @@ -165,28 +264,60 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +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.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 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= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +k8s.io/api v0.26.9 h1:s8Y+G1u2JM55b90+Yo2RVb3PGT/hkWNVPN4idPERxJg= +k8s.io/api v0.26.9/go.mod h1:W/W4fEWRVzPD36820LlVUQfNBiSbiq0VPWRFJKwzmUg= k8s.io/apimachinery v0.27.4 h1:CdxflD4AF61yewuid0fLl6bM4a3q04jWel0IlP+aYjs= k8s.io/apimachinery v0.27.4/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= +k8s.io/client-go v0.26.9 h1:TGWi/6guEjIgT0Hg871Gsmx0qFuoGyGFjlFedrk7It0= +k8s.io/client-go v0.26.9/go.mod h1:tU1FZS0bwAmAFyPYpZycUQrQnUMzQ5MHloop7EbX6ow= k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= +k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 h1:kmDqav+P+/5e1i9tFfHq1qcF3sOrDp+YEkVDAHu7Jwk= k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/integration-tests/integration_test.go b/integration-tests/integration_test.go index d828721b76..4819b9dd94 100644 --- a/integration-tests/integration_test.go +++ b/integration-tests/integration_test.go @@ -5,9 +5,9 @@ import ( "github.com/stretchr/testify/suite" + "github.com/stackrox/collector/integration-tests/pkg/config" + "github.com/stackrox/collector/integration-tests/pkg/types" "github.com/stackrox/collector/integration-tests/suites" - "github.com/stackrox/collector/integration-tests/suites/config" - "github.com/stackrox/collector/integration-tests/suites/types" ) func TestProcessNetwork(t *testing.T) { diff --git a/integration-tests/k8s_test.go b/integration-tests/k8s_test.go new file mode 100644 index 0000000000..4e7903e3c6 --- /dev/null +++ b/integration-tests/k8s_test.go @@ -0,0 +1,19 @@ +//go:build k8s +// +build k8s + +package integrationtests + +import ( + "testing" + + "github.com/stretchr/testify/suite" + + "github.com/stackrox/collector/integration-tests/suites" +) + +func TestK8sNamespace(t *testing.T) { + if testing.Short() { + t.Skip("Not running k8s in short mode") + } + suite.Run(t, new(suites.K8sNamespaceTestSuite)) +} diff --git a/integration-tests/pkg/collector/collector.go b/integration-tests/pkg/collector/collector.go new file mode 100644 index 0000000000..85d55ff367 --- /dev/null +++ b/integration-tests/pkg/collector/collector.go @@ -0,0 +1,45 @@ +package collector + +import ( + "os" + "path/filepath" + "strings" + + "github.com/stackrox/collector/integration-tests/pkg/config" + "github.com/stackrox/collector/integration-tests/pkg/executor" +) + +type StartupOptions struct { + Mounts map[string]string + Env map[string]string + Config map[string]any + BootstrapOnly bool +} + +type Manager interface { + Setup(options *StartupOptions) error + Launch() error + TearDown() error + IsRunning() (bool, error) + ContainerID() string + TestName() string +} + +func New(e executor.Executor, name string) Manager { + k8sExec, ok := e.(*executor.K8sExecutor) + if ok { + return newK8sManager(*k8sExec, name) + } + return newDockerManager(e, name) +} + +func prepareLog(m Manager) (*os.File, error) { + logDirectory := filepath.Join(".", "container-logs", config.VMInfo().Config, config.CollectionMethod()) + err := os.MkdirAll(logDirectory, os.ModePerm) + if err != nil { + return nil, err + } + + logPath := filepath.Join(logDirectory, strings.ReplaceAll(m.TestName(), "/", "_")+"-collector.log") + return os.Create(logPath) +} diff --git a/integration-tests/suites/common/collector_manager.go b/integration-tests/pkg/collector/collector_docker.go similarity index 63% rename from integration-tests/suites/common/collector_manager.go rename to integration-tests/pkg/collector/collector_docker.go index 69d7905430..8db47112ac 100644 --- a/integration-tests/suites/common/collector_manager.go +++ b/integration-tests/pkg/collector/collector_docker.go @@ -1,28 +1,20 @@ -package common +package collector import ( "encoding/json" "fmt" - "io/ioutil" - "os" - "path/filepath" "strings" "golang.org/x/exp/maps" "github.com/hashicorp/go-multierror" - "github.com/stackrox/collector/integration-tests/suites/config" + "github.com/stackrox/collector/integration-tests/pkg/common" + "github.com/stackrox/collector/integration-tests/pkg/config" + "github.com/stackrox/collector/integration-tests/pkg/executor" ) -type CollectorStartupOptions struct { - Mounts map[string]string - Env map[string]string - Config map[string]any - BootstrapOnly bool -} - -type CollectorManager struct { - executor Executor +type DockerCollectorManager struct { + executor executor.Executor mounts map[string]string env map[string]string config map[string]any @@ -30,10 +22,10 @@ type CollectorManager struct { testName string CollectorOutput string - ContainerID string + containerID string } -func NewCollectorManager(e Executor, name string) *CollectorManager { +func newDockerManager(e executor.Executor, name string) *DockerCollectorManager { collectorOptions := config.CollectorInfo() collectionMethod := config.CollectionMethod() @@ -66,7 +58,7 @@ func NewCollectorManager(e Executor, name string) *CollectorManager { "/module": "", } - return &CollectorManager{ + return &DockerCollectorManager{ executor: e, bootstrapOnly: false, env: env, @@ -76,11 +68,11 @@ func NewCollectorManager(e Executor, name string) *CollectorManager { } } -func (c *CollectorManager) Setup(options *CollectorStartupOptions) error { +func (c *DockerCollectorManager) Setup(options *StartupOptions) error { if options == nil { // default to empty, if no options are provided (i.e. use the // default values) - options = &CollectorStartupOptions{} + options = &StartupOptions{} } if options.Env != nil { @@ -98,24 +90,24 @@ func (c *CollectorManager) Setup(options *CollectorStartupOptions) error { return c.executor.PullImage(config.Images().CollectorImage()) } -func (c *CollectorManager) Launch() error { +func (c *DockerCollectorManager) Launch() error { return c.launchCollector() } -func (c *CollectorManager) TearDown() error { +func (c *DockerCollectorManager) TearDown() error { isRunning, err := c.IsRunning() if err != nil { - fmt.Println("Error: Checking if container running") - return err + return fmt.Errorf("Unable to check if container is running: %s", err) } if !isRunning { c.captureLogs("collector") // Check if collector container segfaulted or exited with error - exitCode, err := c.executor.ExitCode("collector") + exitCode, err := c.executor.ExitCode(executor.ContainerFilter{ + Name: "collector", + }) if err != nil { - fmt.Println("Error: Container not running") - return err + return fmt.Errorf("Failed to get container exit code: %s", err) } if exitCode != 0 { return fmt.Errorf("Collector container has non-zero exit code (%d)", exitCode) @@ -129,27 +121,27 @@ func (c *CollectorManager) TearDown() error { return nil } -func (c *CollectorManager) IsRunning() (bool, error) { +func (c *DockerCollectorManager) IsRunning() (bool, error) { return c.executor.IsContainerRunning("collector") } // These two methods might be useful in the future. I used them for debugging -func (c *CollectorManager) getContainers() (string, error) { - cmd := []string{RuntimeCommand, "container", "ps"} +func (c *DockerCollectorManager) getContainers() (string, error) { + cmd := []string{executor.RuntimeCommand, "container", "ps"} containers, err := c.executor.Exec(cmd...) return containers, err } -func (c *CollectorManager) getAllContainers() (string, error) { - cmd := []string{RuntimeCommand, "container", "ps", "-a"} +func (c *DockerCollectorManager) getAllContainers() (string, error) { + cmd := []string{executor.RuntimeCommand, "container", "ps", "-a"} containers, err := c.executor.Exec(cmd...) return containers, err } -func (c *CollectorManager) launchCollector() error { - cmd := []string{RuntimeCommand, "run", +func (c *DockerCollectorManager) launchCollector() error { + cmd := []string{executor.RuntimeCommand, "run", "--name", "collector", "--privileged", "--network=host"} @@ -187,29 +179,32 @@ func (c *CollectorManager) launchCollector() error { c.CollectorOutput = output outLines := strings.Split(output, "\n") - c.ContainerID = ContainerShortID(string(outLines[len(outLines)-1])) + c.containerID = common.ContainerShortID(string(outLines[len(outLines)-1])) return err } -func (c *CollectorManager) captureLogs(containerName string) (string, error) { - logs, err := c.executor.Exec(RuntimeCommand, "logs", containerName) +func (c *DockerCollectorManager) captureLogs(containerName string) (string, error) { + logs, err := c.executor.Exec(executor.RuntimeCommand, "logs", containerName) if err != nil { - fmt.Printf(RuntimeCommand+" logs error (%v) for container %s\n", err, containerName) + fmt.Printf(executor.RuntimeCommand+" logs error (%v) for container %s\n", err, containerName) return "", err } - logDirectory := filepath.Join(".", "container-logs", config.VMInfo().Config, config.CollectionMethod()) - os.MkdirAll(logDirectory, os.ModePerm) - logFile := filepath.Join(logDirectory, strings.ReplaceAll(c.testName, "/", "_")+"-"+containerName+".log") - err = ioutil.WriteFile(logFile, []byte(logs), 0644) + + logFile, err := prepareLog(c) if err != nil { return "", err } + defer logFile.Close() + + _, err = logFile.WriteString(logs) return logs, nil } -func (c *CollectorManager) killContainer(name string) error { +func (c *DockerCollectorManager) killContainer(name string) error { _, err1 := c.executor.KillContainer(name) - _, err2 := c.executor.RemoveContainer(name) + _, err2 := c.executor.RemoveContainer(executor.ContainerFilter{ + Name: name, + }) var result error if err1 != nil { @@ -222,7 +217,15 @@ func (c *CollectorManager) killContainer(name string) error { return result } -func (c *CollectorManager) stopContainer(name string) error { +func (c *DockerCollectorManager) stopContainer(name string) error { _, err := c.executor.StopContainer(name) return err } + +func (c *DockerCollectorManager) ContainerID() string { + return c.containerID +} + +func (c *DockerCollectorManager) TestName() string { + return c.testName +} diff --git a/integration-tests/pkg/collector/collector_k8s.go b/integration-tests/pkg/collector/collector_k8s.go new file mode 100644 index 0000000000..0ea0f78df8 --- /dev/null +++ b/integration-tests/pkg/collector/collector_k8s.go @@ -0,0 +1,215 @@ +package collector + +import ( + "context" + "encoding/json" + "fmt" + "io" + + "github.com/stackrox/collector/integration-tests/pkg/config" + "github.com/stackrox/collector/integration-tests/pkg/executor" + "golang.org/x/exp/maps" + + coreV1 "k8s.io/api/core/v1" + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + TEST_NAMESPACE = "collector-tests" +) + +type K8sCollectorManager struct { + executor executor.K8sExecutor + volumeMounts []coreV1.VolumeMount + volumes []coreV1.Volume + env []coreV1.EnvVar + config map[string]any + + testName string +} + +func newK8sManager(e executor.K8sExecutor, name string) *K8sCollectorManager { + collectorOptions := config.CollectorInfo() + collectionMethod := config.CollectionMethod() + + collectorConfig := map[string]any{ + "logLevel": collectorOptions.LogLevel, + "turnOffScrape": true, + "scrapeInterval": 2, + } + + env := []coreV1.EnvVar{ + {Name: "GRPC_SERVER", Value: "tester-svc:9999"}, + {Name: "COLLECTION_METHOD", Value: collectionMethod}, + {Name: "COLLECTOR_PRE_ARGUMENTS", Value: collectorOptions.PreArguments}, + {Name: "ENABLE_CORE_DUMP", Value: "false"}, + } + + if !collectorOptions.Offline { + env = append(env, coreV1.EnvVar{Name: "MODULE_DOWNLOAD_BASE_URL", Value: "https://collector-modules.stackrox.io/612dd2ee06b660e728292de9393e18c81a88f347ec52a39207c5166b5302b656"}) + } + + propagationHostToContainer := coreV1.MountPropagationHostToContainer + mounts := []coreV1.VolumeMount{ + {Name: "proc-ro", ReadOnly: true, MountPath: "/host/proc", MountPropagation: &propagationHostToContainer}, + {Name: "etc-ro", ReadOnly: true, MountPath: "/host/etc", MountPropagation: &propagationHostToContainer}, + {Name: "usr-ro", ReadOnly: true, MountPath: "/host/usr/lib", MountPropagation: &propagationHostToContainer}, + {Name: "sys-ro", ReadOnly: true, MountPath: "/host/sys/kernel/debug", MountPropagation: &propagationHostToContainer}, + {Name: "var-rw", ReadOnly: false, MountPath: "/host/var", MountPropagation: &propagationHostToContainer}, + {Name: "run-rw", ReadOnly: false, MountPath: "/host/run", MountPropagation: &propagationHostToContainer}, + {Name: "tmp", ReadOnly: false, MountPath: "/tmp", MountPropagation: &propagationHostToContainer}, + {Name: "module", ReadOnly: false, MountPath: "/module"}, + } + + volumes := []coreV1.Volume{ + {Name: "proc-ro", VolumeSource: coreV1.VolumeSource{HostPath: &coreV1.HostPathVolumeSource{Path: "/proc"}}}, + {Name: "etc-ro", VolumeSource: coreV1.VolumeSource{HostPath: &coreV1.HostPathVolumeSource{Path: "/etc"}}}, + {Name: "usr-ro", VolumeSource: coreV1.VolumeSource{HostPath: &coreV1.HostPathVolumeSource{Path: "/usr/lib"}}}, + {Name: "sys-ro", VolumeSource: coreV1.VolumeSource{HostPath: &coreV1.HostPathVolumeSource{Path: "/sys/kernel/debug"}}}, + {Name: "var-rw", VolumeSource: coreV1.VolumeSource{HostPath: &coreV1.HostPathVolumeSource{Path: "/var"}}}, + {Name: "run-rw", VolumeSource: coreV1.VolumeSource{HostPath: &coreV1.HostPathVolumeSource{Path: "/run"}}}, + {Name: "tmp", VolumeSource: coreV1.VolumeSource{HostPath: &coreV1.HostPathVolumeSource{Path: "/tmp"}}}, + {Name: "module", VolumeSource: coreV1.VolumeSource{EmptyDir: &coreV1.EmptyDirVolumeSource{}}}, + } + + return &K8sCollectorManager{ + executor: e, + volumeMounts: mounts, + volumes: volumes, + env: env, + config: collectorConfig, + testName: name, + } +} + +func (k *K8sCollectorManager) Setup(options *StartupOptions) error { + if options == nil { + // default values + options = &StartupOptions{} + } + + for name, value := range options.Env { + k.env = replaceOrAppendEnvVar(k.env, coreV1.EnvVar{Name: name, Value: value}) + } + + configJson, err := json.Marshal(k.config) + if err != nil { + return err + } + k.env = replaceOrAppendEnvVar(k.env, coreV1.EnvVar{Name: "COLLECTOR_CONFIG", Value: string(configJson)}) + + if options.Config != nil { + maps.Copy(k.config, options.Config) + } + + return nil +} + +func (k *K8sCollectorManager) Launch() error { + objectMeta := metaV1.ObjectMeta{ + Name: "collector", + Namespace: TEST_NAMESPACE, + Labels: map[string]string{"app": "collector"}, + } + + privileged := true + container := coreV1.Container{ + Name: "collector", + Image: config.Images().CollectorImage(), + Ports: []coreV1.ContainerPort{{ContainerPort: 8080}}, + Env: k.env, + VolumeMounts: k.volumeMounts, + SecurityContext: &coreV1.SecurityContext{Privileged: &privileged}, + } + + pod := &coreV1.Pod{ + ObjectMeta: objectMeta, + Spec: coreV1.PodSpec{ + Containers: []coreV1.Container{container}, + Volumes: k.volumes, + RestartPolicy: coreV1.RestartPolicyNever, // if the pod fails, it fails + }, + } + + _, err := k.executor.CreatePod(TEST_NAMESPACE, pod) + return err +} + +func (k *K8sCollectorManager) TearDown() error { + isRunning, err := k.IsRunning() + if err != nil { + return err + } + + err = k.captureLogs() + if err != nil { + return fmt.Errorf("Failed to get collector logs: %s", err) + } + + if !isRunning { + exitCode, err := k.executor.ExitCode(executor.ContainerFilter{ + Name: "collector", + Namespace: TEST_NAMESPACE, + }) + if err != nil { + return fmt.Errorf("Failed to get container exit code: %s", err) + } + + if exitCode != 0 { + return fmt.Errorf("Collector container has non-zero exit code (%d)", exitCode) + } + } + + return k.executor.ClientSet().CoreV1().Pods(TEST_NAMESPACE).Delete(context.Background(), "collector", metaV1.DeleteOptions{}) +} + +func (k *K8sCollectorManager) IsRunning() (bool, error) { + pod, err := k.executor.ClientSet().CoreV1().Pods(TEST_NAMESPACE).Get(context.Background(), "collector", metaV1.GetOptions{}) + if err != nil { + return false, err + } + + return *pod.Status.ContainerStatuses[0].Started, nil +} + +func (k *K8sCollectorManager) ContainerID() string { + cf := executor.ContainerFilter{ + Name: "collector", + Namespace: TEST_NAMESPACE, + } + + return k.executor.ContainerID(cf) +} + +func (k *K8sCollectorManager) TestName() string { + return k.testName +} + +func replaceOrAppendEnvVar(list []coreV1.EnvVar, newVar coreV1.EnvVar) []coreV1.EnvVar { + for _, envVar := range list { + if envVar.Name == newVar.Name { + envVar.Value = newVar.Value + return list + } + } + + return append(list, newVar) +} + +func (k *K8sCollectorManager) captureLogs() error { + req := k.executor.ClientSet().CoreV1().Pods(TEST_NAMESPACE).GetLogs("collector", &coreV1.PodLogOptions{}) + podLogs, err := req.Stream(context.Background()) + if err != nil { + return err + } + defer podLogs.Close() + + logFile, err := prepareLog(k) + if err != nil { + return err + } + defer logFile.Close() + + _, err = io.Copy(logFile, podLogs) + return err +} diff --git a/integration-tests/suites/common/utils.go b/integration-tests/pkg/common/utils.go similarity index 100% rename from integration-tests/suites/common/utils.go rename to integration-tests/pkg/common/utils.go diff --git a/integration-tests/suites/config/config.go b/integration-tests/pkg/config/config.go similarity index 100% rename from integration-tests/suites/config/config.go rename to integration-tests/pkg/config/config.go diff --git a/integration-tests/suites/config/env.go b/integration-tests/pkg/config/env.go similarity index 100% rename from integration-tests/suites/config/env.go rename to integration-tests/pkg/config/env.go diff --git a/integration-tests/suites/config/images.go b/integration-tests/pkg/config/images.go similarity index 100% rename from integration-tests/suites/config/images.go rename to integration-tests/pkg/config/images.go diff --git a/integration-tests/pkg/executor/executor.go b/integration-tests/pkg/executor/executor.go new file mode 100644 index 0000000000..a14478a0b3 --- /dev/null +++ b/integration-tests/pkg/executor/executor.go @@ -0,0 +1,40 @@ +package executor + +import ( + "os/exec" + + "github.com/stackrox/collector/integration-tests/pkg/config" +) + +type ContainerFilter struct { + Name string + Namespace string +} + +type Executor interface { + CopyFromHost(src string, dst string) (string, error) + PullImage(image string) error + IsContainerRunning(container string) (bool, error) + ContainerExists(filter ContainerFilter) (bool, error) + ContainerID(filter ContainerFilter) string + ExitCode(filter ContainerFilter) (int, error) + Exec(args ...string) (string, error) + ExecWithErrorCheck(errCheckFn func(string, error) error, args ...string) (string, error) + ExecWithStdin(pipedContent string, args ...string) (string, error) + ExecWithoutRetry(args ...string) (string, error) + KillContainer(name string) (string, error) + RemoveContainer(filter ContainerFilter) (string, error) + StopContainer(name string) (string, error) +} + +type CommandBuilder interface { + ExecCommand(args ...string) *exec.Cmd + RemoteCopyCommand(remoteSrc string, localDst string) *exec.Cmd +} + +func New() (Executor, error) { + if config.HostInfo().Kind == "k8s" { + return newK8sExecutor() + } + return newDockerExecutor() +} diff --git a/integration-tests/suites/common/executor.go b/integration-tests/pkg/executor/executor_docker.go similarity index 71% rename from integration-tests/suites/common/executor.go rename to integration-tests/pkg/executor/executor_docker.go index a8a4c4b7be..e9537169b3 100644 --- a/integration-tests/suites/common/executor.go +++ b/integration-tests/pkg/executor/executor_docker.go @@ -1,4 +1,4 @@ -package common +package executor import ( "fmt" @@ -8,8 +8,8 @@ import ( "strings" "github.com/pkg/errors" - - "github.com/stackrox/collector/integration-tests/suites/config" + "github.com/stackrox/collector/integration-tests/pkg/common" + "github.com/stackrox/collector/integration-tests/pkg/config" ) var ( @@ -20,27 +20,7 @@ var ( RuntimeAsRoot = config.RuntimeInfo().RunAsRoot ) -type Executor interface { - CopyFromHost(src string, dst string) (string, error) - PullImage(image string) error - IsContainerRunning(image string) (bool, error) - ContainerExists(container string) (bool, error) - ExitCode(container string) (int, error) - Exec(args ...string) (string, error) - ExecWithErrorCheck(errCheckFn func(string, error) error, args ...string) (string, error) - ExecWithStdin(pipedContent string, args ...string) (string, error) - ExecWithoutRetry(args ...string) (string, error) - KillContainer(name string) (string, error) - RemoveContainer(name string) (string, error) - StopContainer(name string) (string, error) -} - -type CommandBuilder interface { - ExecCommand(args ...string) *exec.Cmd - RemoteCopyCommand(remoteSrc string, localDst string) *exec.Cmd -} - -type executor struct { +type dockerExecutor struct { builder CommandBuilder } @@ -60,7 +40,7 @@ type gcloudCommandBuilder struct { type localCommandBuilder struct { } -func NewSSHCommandBuilder() CommandBuilder { +func newSSHCommandBuilder() CommandBuilder { host_info := config.HostInfo() return &sshCommandBuilder{ user: host_info.User, @@ -69,7 +49,7 @@ func NewSSHCommandBuilder() CommandBuilder { } } -func NewGcloudCommandBuilder() CommandBuilder { +func newGcloudCommandBuilder() CommandBuilder { host_info := config.HostInfo() gcb := &gcloudCommandBuilder{ user: host_info.User, @@ -83,25 +63,25 @@ func NewGcloudCommandBuilder() CommandBuilder { return gcb } -func NewLocalCommandBuilder() CommandBuilder { +func newLocalCommandBuilder() CommandBuilder { return &localCommandBuilder{} } -func NewExecutor() Executor { - e := executor{} +func newDockerExecutor() (*dockerExecutor, error) { + e := dockerExecutor{} switch config.HostInfo().Kind { case "ssh": - e.builder = NewSSHCommandBuilder() + e.builder = newSSHCommandBuilder() case "gcloud": - e.builder = NewGcloudCommandBuilder() + e.builder = newGcloudCommandBuilder() case "local": - e.builder = NewLocalCommandBuilder() + e.builder = newLocalCommandBuilder() } - return &e + return &e, nil } // Exec executes the provided command with retries on non-zero error from the command. -func (e *executor) Exec(args ...string) (string, error) { +func (e *dockerExecutor) Exec(args ...string) (string, error) { if args[0] == RuntimeCommand && RuntimeAsRoot { args = append([]string{"sudo"}, args...) } @@ -112,7 +92,7 @@ func (e *executor) Exec(args ...string) (string, error) { // ExecWithErrorCheck executes the provided command, retrying if an error occurs // and the command's output does not contain any of the accepted output contents. -func (e *executor) ExecWithErrorCheck(errCheckFn func(string, error) error, args ...string) (string, error) { +func (e *dockerExecutor) ExecWithErrorCheck(errCheckFn func(string, error) error, args ...string) (string, error) { if args[0] == RuntimeCommand && RuntimeAsRoot { args = append([]string{"sudo"}, args...) } @@ -122,14 +102,14 @@ func (e *executor) ExecWithErrorCheck(errCheckFn func(string, error) error, args } // ExecWithoutRetry executes provided command once, without retries. -func (e *executor) ExecWithoutRetry(args ...string) (string, error) { +func (e *dockerExecutor) ExecWithoutRetry(args ...string) (string, error) { if args[0] == RuntimeCommand && RuntimeAsRoot { args = append([]string{"sudo"}, args...) } return e.RunCommand(e.builder.ExecCommand(args...)) } -func (e *executor) RunCommand(cmd *exec.Cmd) (string, error) { +func (e *dockerExecutor) RunCommand(cmd *exec.Cmd) (string, error) { if cmd == nil { return "", nil } @@ -148,7 +128,7 @@ func (e *executor) RunCommand(cmd *exec.Cmd) (string, error) { return trimmed, err } -func (e *executor) ExecWithStdin(pipedContent string, args ...string) (res string, err error) { +func (e *dockerExecutor) ExecWithStdin(pipedContent string, args ...string) (res string, err error) { if args[0] == RuntimeCommand && RuntimeAsRoot { args = append([]string{"sudo"}, args...) @@ -169,7 +149,7 @@ func (e *executor) ExecWithStdin(pipedContent string, args ...string) (res strin return e.RunCommand(cmd) } -func (e *executor) CopyFromHost(src string, dst string) (res string, err error) { +func (e *dockerExecutor) CopyFromHost(src string, dst string) (res string, err error) { maxAttempts := 3 attempt := 0 for attempt < maxAttempts { @@ -186,7 +166,7 @@ func (e *executor) CopyFromHost(src string, dst string) (res string, err error) return res, err } -func (e *executor) PullImage(image string) error { +func (e *dockerExecutor) PullImage(image string) error { _, err := e.Exec(RuntimeCommand, "image", "inspect", image) if err == nil { return nil @@ -195,7 +175,7 @@ func (e *executor) PullImage(image string) error { return err } -func (e *executor) IsContainerRunning(containerID string) (bool, error) { +func (e *dockerExecutor) IsContainerRunning(containerID string) (bool, error) { result, err := e.ExecWithoutRetry(RuntimeCommand, "inspect", containerID, "--format='{{.State.Running}}'") if err != nil { return false, err @@ -203,16 +183,25 @@ func (e *executor) IsContainerRunning(containerID string) (bool, error) { return strconv.ParseBool(strings.Trim(result, "\"'")) } -func (e *executor) ContainerExists(container string) (bool, error) { - _, err := e.ExecWithoutRetry(RuntimeCommand, "inspect", container) +func (e *dockerExecutor) ContainerID(cf ContainerFilter) string { + result, err := e.ExecWithoutRetry(RuntimeCommand, "ps", "-aqf", "name=^"+cf.Name+"$") + if err != nil { + return "" + } + + return strings.Trim(result, "\"") +} + +func (e *dockerExecutor) ContainerExists(cf ContainerFilter) (bool, error) { + _, err := e.ExecWithoutRetry(RuntimeCommand, "inspect", cf.Name) if err != nil { return false, err } return true, nil } -func (e *executor) ExitCode(containerID string) (int, error) { - result, err := e.Exec(RuntimeCommand, "inspect", containerID, "--format='{{.State.ExitCode}}'") +func (e *dockerExecutor) ExitCode(cf ContainerFilter) (int, error) { + result, err := e.Exec(RuntimeCommand, "inspect", cf.Name, "--format='{{.State.ExitCode}}'") if err != nil { return -1, err } @@ -241,17 +230,17 @@ func containerErrorCheckFunction(name string, cmd string) func(string, error) er } // KillContainer runs the kill operation on the provided container name -func (e *executor) KillContainer(name string) (string, error) { +func (e *dockerExecutor) KillContainer(name string) (string, error) { return e.ExecWithErrorCheck(containerErrorCheckFunction(name, "kill"), RuntimeCommand, "kill", name) } // RemoveContainer runs the remove operation on the provided container name -func (e *executor) RemoveContainer(name string) (string, error) { - return e.ExecWithErrorCheck(containerErrorCheckFunction(name, "remove"), RuntimeCommand, "rm", name) +func (e *dockerExecutor) RemoveContainer(cf ContainerFilter) (string, error) { + return e.ExecWithErrorCheck(containerErrorCheckFunction(cf.Name, "remove"), RuntimeCommand, "rm", cf.Name) } // StopContainer runs the stop operation on the provided container name -func (e *executor) StopContainer(name string) (string, error) { +func (e *dockerExecutor) StopContainer(name string) (string, error) { return e.ExecWithErrorCheck(containerErrorCheckFunction(name, "stop"), RuntimeCommand, "stop", name) } @@ -278,7 +267,7 @@ func (e *gcloudCommandBuilder) ExecCommand(args ...string) *exec.Cmd { } cmdArgs = append(cmdArgs, userInstance, "--", "-T") - cmdArgs = append(cmdArgs, QuoteArgs(args)...) + cmdArgs = append(cmdArgs, common.QuoteArgs(args)...) return exec.Command("gcloud", cmdArgs...) } @@ -301,7 +290,7 @@ func (e *sshCommandBuilder) ExecCommand(args ...string) *exec.Cmd { "-o", "StrictHostKeyChecking=no", "-i", e.keyPath, e.user + "@" + e.address} - cmdArgs = append(cmdArgs, QuoteArgs(args)...) + cmdArgs = append(cmdArgs, common.QuoteArgs(args)...) return exec.Command("ssh", cmdArgs...) } diff --git a/integration-tests/pkg/executor/executor_k8s.go b/integration-tests/pkg/executor/executor_k8s.go new file mode 100644 index 0000000000..1d5425a3f0 --- /dev/null +++ b/integration-tests/pkg/executor/executor_k8s.go @@ -0,0 +1,174 @@ +package executor + +import ( + "context" + "fmt" + "strings" + + "github.com/stackrox/collector/integration-tests/pkg/common" + coreV1 "k8s.io/api/core/v1" + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" +) + +const ( + TESTS_NAMESPACE = "collector-tests" +) + +type K8sExecutor struct { + clientset *kubernetes.Clientset +} + +func newK8sExecutor() (*K8sExecutor, error) { + fmt.Println("Creating k8s configuration") + config, err := rest.InClusterConfig() + if err != nil { + fmt.Printf("Error: Failed to get cluster config: %s\n", err) + return nil, err + } + + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + fmt.Printf("Error: Failed to create client: %s", err) + return nil, err + } + + k8s := &K8sExecutor{ + clientset: clientset, + } + return k8s, nil +} + +func (e *K8sExecutor) CopyFromHost(src string, dst string) (string, error) { + return "", fmt.Errorf("Unimplemented") +} + +func (e *K8sExecutor) PullImage(image string) error { + return fmt.Errorf("Unimplemented") +} + +func (e *K8sExecutor) IsContainerRunning(podName string) (bool, error) { + pod, err := e.clientset.CoreV1().Pods(TESTS_NAMESPACE).Get(context.Background(), podName, metaV1.GetOptions{}) + if err != nil { + return false, err + } + + if pod == nil || pod.Status.Phase != coreV1.PodRunning { + return false, nil + } + + return pod.Status.ContainerStatuses[0].Ready, nil +} + +func (e *K8sExecutor) ContainerID(podFilter ContainerFilter) string { + pod, err := e.ClientSet().CoreV1().Pods(podFilter.Namespace).Get(context.Background(), podFilter.Name, metaV1.GetOptions{}) + if err != nil { + fmt.Printf("%s\n", err) + return "" + } + + if len(pod.Status.ContainerStatuses) != 1 { + return "" + } + + /* + * The format extracted from the following line looks something like this: + * containerd://01e8c0454972a6b22b2e8ff7bf5a7d011e7dc7c0cde95c468a823b7085669a36 + */ + containerID := pod.Status.ContainerStatuses[0].ContainerID + if len(containerID) < 12 { + fmt.Printf("Invalid container ID: %q\n", containerID) + return "" + } + + i := strings.LastIndex(containerID, "/") + if i == -1 { + fmt.Printf("Invalid container ID: %q\n", containerID) + return "" + } + + return common.ContainerShortID(containerID[i+1:]) +} + +func (e *K8sExecutor) ContainerExists(podFilter ContainerFilter) (bool, error) { + pod, err := e.clientset.CoreV1().Pods(podFilter.Namespace).Get(context.Background(), podFilter.Name, metaV1.GetOptions{}) + if err != nil { + return false, err + } + + return pod != nil, nil +} + +func (e *K8sExecutor) ExitCode(podFilter ContainerFilter) (int, error) { + pod, err := e.clientset.CoreV1().Pods(podFilter.Namespace).Get(context.Background(), podFilter.Name, metaV1.GetOptions{}) + if err != nil { + return -1, err + } + + if pod == nil { + return -1, fmt.Errorf("pod does not exist") + } + + terminated := pod.Status.ContainerStatuses[0].State.Terminated + if terminated == nil { + return -1, fmt.Errorf("failed to get termination status") + } + + return int(terminated.ExitCode), nil +} + +func (e *K8sExecutor) Exec(args ...string) (string, error) { + return "", fmt.Errorf("Unimplemented") +} + +func (e *K8sExecutor) ExecWithErrorCheck(errCheckFn func(string, error) error, args ...string) (string, error) { + return "", fmt.Errorf("Unimplemented") +} + +func (e *K8sExecutor) ExecWithStdin(pipedContent string, args ...string) (string, error) { + return "", fmt.Errorf("Unimplemented") +} + +func (e *K8sExecutor) ExecWithoutRetry(args ...string) (string, error) { + return "", fmt.Errorf("Unimplemented") +} + +func (e *K8sExecutor) KillContainer(name string) (string, error) { + return "", fmt.Errorf("Unimplemented") +} + +func (e *K8sExecutor) RemoveContainer(podFilter ContainerFilter) (string, error) { + err := e.clientset.CoreV1().Pods(podFilter.Namespace).Delete(context.Background(), podFilter.Name, metaV1.DeleteOptions{}) + return "", err +} + +func (e *K8sExecutor) StopContainer(name string) (string, error) { + return "", fmt.Errorf("Unimplemented") +} + +func (e *K8sExecutor) CreateNamespace(ns string) (*coreV1.Namespace, error) { + meta := metaV1.ObjectMeta{Name: ns} + return e.clientset.CoreV1().Namespaces().Create(context.Background(), &coreV1.Namespace{ObjectMeta: meta}, metaV1.CreateOptions{}) +} + +func (e *K8sExecutor) NamespaceExists(ns string) (bool, error) { + res, err := e.clientset.CoreV1().Namespaces().Get(context.Background(), ns, metaV1.GetOptions{}) + if err != nil { + return false, err + } + + return res != nil, nil +} + +func (e *K8sExecutor) RemoveNamespace(ns string) error { + return e.clientset.CoreV1().Namespaces().Delete(context.Background(), ns, metaV1.DeleteOptions{}) +} + +func (e *K8sExecutor) CreatePod(ns string, pod *coreV1.Pod) (*coreV1.Pod, error) { + return e.clientset.CoreV1().Pods(ns).Create(context.Background(), pod, metaV1.CreateOptions{}) +} + +func (e *K8sExecutor) ClientSet() *kubernetes.Clientset { + return e.clientset +} diff --git a/integration-tests/suites/common/retry.go b/integration-tests/pkg/executor/retry.go similarity index 97% rename from integration-tests/suites/common/retry.go rename to integration-tests/pkg/executor/retry.go index 324331af6e..1f45890bc1 100644 --- a/integration-tests/suites/common/retry.go +++ b/integration-tests/pkg/executor/retry.go @@ -1,4 +1,4 @@ -package common +package executor import ( "time" diff --git a/integration-tests/suites/mock_sensor/expect_conn.go b/integration-tests/pkg/mock_sensor/expect_conn.go similarity index 98% rename from integration-tests/suites/mock_sensor/expect_conn.go rename to integration-tests/pkg/mock_sensor/expect_conn.go index 64ed6561d1..f6e3828b8a 100644 --- a/integration-tests/suites/mock_sensor/expect_conn.go +++ b/integration-tests/pkg/mock_sensor/expect_conn.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/thoas/go-funk" - "github.com/stackrox/collector/integration-tests/suites/types" + "github.com/stackrox/collector/integration-tests/pkg/types" ) // ExpectConnections waits up to the timeout for the gRPC server to receive diff --git a/integration-tests/suites/mock_sensor/expect_proc.go b/integration-tests/pkg/mock_sensor/expect_proc.go similarity index 98% rename from integration-tests/suites/mock_sensor/expect_proc.go rename to integration-tests/pkg/mock_sensor/expect_proc.go index b4e0bf2108..884f9bb595 100644 --- a/integration-tests/suites/mock_sensor/expect_proc.go +++ b/integration-tests/pkg/mock_sensor/expect_proc.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/thoas/go-funk" - "github.com/stackrox/collector/integration-tests/suites/types" + "github.com/stackrox/collector/integration-tests/pkg/types" ) func (s *MockSensor) ExpectProcessesN(t *testing.T, containerID string, timeout time.Duration, n int) []types.ProcessInfo { diff --git a/integration-tests/suites/mock_sensor/ring.go b/integration-tests/pkg/mock_sensor/ring.go similarity index 100% rename from integration-tests/suites/mock_sensor/ring.go rename to integration-tests/pkg/mock_sensor/ring.go diff --git a/integration-tests/suites/mock_sensor/server.go b/integration-tests/pkg/mock_sensor/server.go similarity index 99% rename from integration-tests/suites/mock_sensor/server.go rename to integration-tests/pkg/mock_sensor/server.go index 1bfe9704af..c2cbe88eb0 100644 --- a/integration-tests/suites/mock_sensor/server.go +++ b/integration-tests/pkg/mock_sensor/server.go @@ -17,8 +17,8 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/keepalive" - "github.com/stackrox/collector/integration-tests/suites/config" - "github.com/stackrox/collector/integration-tests/suites/types" + "github.com/stackrox/collector/integration-tests/pkg/config" + "github.com/stackrox/collector/integration-tests/pkg/types" ) const ( diff --git a/integration-tests/suites/types/endpoint.go b/integration-tests/pkg/types/endpoint.go similarity index 100% rename from integration-tests/suites/types/endpoint.go rename to integration-tests/pkg/types/endpoint.go diff --git a/integration-tests/suites/types/listen_address.go b/integration-tests/pkg/types/listen_address.go similarity index 100% rename from integration-tests/suites/types/listen_address.go rename to integration-tests/pkg/types/listen_address.go diff --git a/integration-tests/suites/types/network.go b/integration-tests/pkg/types/network.go similarity index 100% rename from integration-tests/suites/types/network.go rename to integration-tests/pkg/types/network.go diff --git a/integration-tests/suites/types/process.go b/integration-tests/pkg/types/process.go similarity index 100% rename from integration-tests/suites/types/process.go rename to integration-tests/pkg/types/process.go diff --git a/integration-tests/suites/types/process_originator.go b/integration-tests/pkg/types/process_originator.go similarity index 100% rename from integration-tests/suites/types/process_originator.go rename to integration-tests/pkg/types/process_originator.go diff --git a/integration-tests/suites/async_connections.go b/integration-tests/suites/async_connections.go index c140aed38e..1b7a1c9339 100644 --- a/integration-tests/suites/async_connections.go +++ b/integration-tests/suites/async_connections.go @@ -7,8 +7,9 @@ import ( "github.com/stretchr/testify/assert" - "github.com/stackrox/collector/integration-tests/suites/common" - "github.com/stackrox/collector/integration-tests/suites/config" + "github.com/stackrox/collector/integration-tests/pkg/collector" + "github.com/stackrox/collector/integration-tests/pkg/common" + "github.com/stackrox/collector/integration-tests/pkg/config" ) type AsyncConnectionTestSuite struct { @@ -34,7 +35,7 @@ func (s *AsyncConnectionTestSuite) SetupSuite() { s.RegisterCleanup("server", "client") s.StartContainerStats() - collectorOptions := common.CollectorStartupOptions{ + collectorOptions := collector.StartupOptions{ Env: map[string]string{ "ROX_COLLECT_CONNECTION_STATUS": strconv.FormatBool(!s.DisableConnectionStatusTracking), }, diff --git a/integration-tests/suites/base.go b/integration-tests/suites/base.go index 94e3f4582f..7bef6d06c1 100644 --- a/integration-tests/suites/base.go +++ b/integration-tests/suites/base.go @@ -14,10 +14,12 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" - "github.com/stackrox/collector/integration-tests/suites/common" - "github.com/stackrox/collector/integration-tests/suites/config" - "github.com/stackrox/collector/integration-tests/suites/mock_sensor" - "github.com/stackrox/collector/integration-tests/suites/types" + "github.com/stackrox/collector/integration-tests/pkg/collector" + "github.com/stackrox/collector/integration-tests/pkg/common" + "github.com/stackrox/collector/integration-tests/pkg/config" + "github.com/stackrox/collector/integration-tests/pkg/executor" + "github.com/stackrox/collector/integration-tests/pkg/mock_sensor" + "github.com/stackrox/collector/integration-tests/pkg/types" ) const ( @@ -35,8 +37,8 @@ const ( type IntegrationTestSuiteBase struct { suite.Suite - executor common.Executor - collector *common.CollectorManager + executor executor.Executor + collector collector.Manager sensor *mock_sensor.MockSensor metrics map[string]float64 stats []ContainerStat @@ -62,7 +64,7 @@ type PerformanceResult struct { // StartCollector will start the collector container and optionally // start the MockSensor, if disableGRPC is false. -func (s *IntegrationTestSuiteBase) StartCollector(disableGRPC bool, options *common.CollectorStartupOptions) { +func (s *IntegrationTestSuiteBase) StartCollector(disableGRPC bool, options *collector.StartupOptions) { if !disableGRPC { s.Sensor().Start() } @@ -75,7 +77,7 @@ func (s *IntegrationTestSuiteBase) StartCollector(disableGRPC bool, options *com // get an error, treat it as no health check was found for the sake of // robustness. hasHealthCheck, err := s.findContainerHealthCheck("collector", - s.Collector().ContainerID) + s.Collector().ContainerID()) if hasHealthCheck && err == nil { // Wait for collector to report healthy, includes initial setup and @@ -83,7 +85,7 @@ func (s *IntegrationTestSuiteBase) StartCollector(disableGRPC bool, options *com // it to 1 min. _, err := s.waitForContainerToBecomeHealthy( "collector", - s.Collector().ContainerID, + s.Collector().ContainerID(), defaultWaitTickSeconds, 5*time.Minute) s.Require().NoError(err) } else { @@ -95,7 +97,7 @@ func (s *IntegrationTestSuiteBase) StartCollector(disableGRPC bool, options *com // wait for the canary process to guarantee collector is started selfCheckOk := s.Sensor().WaitProcessesN( - s.Collector().ContainerID, 30*time.Second, 1, func() { + s.Collector().ContainerID(), 30*time.Second, 1, func() { // Self-check process is not going to be sent via GRPC, instead // create at least one canary process to make sure everything is // fine. @@ -119,18 +121,20 @@ func (s *IntegrationTestSuiteBase) StopCollector() { // one if it is nil. This function can be used to get the object before // the container is launched, so that Collector settings can be adjusted // by individual test suites -func (s *IntegrationTestSuiteBase) Collector() *common.CollectorManager { +func (s *IntegrationTestSuiteBase) Collector() collector.Manager { if s.collector == nil { - s.collector = common.NewCollectorManager(s.Executor(), s.T().Name()) + s.collector = collector.New(s.Executor(), s.T().Name()) } return s.collector } // Executor returns the current executor object, or initializes a new one // if it is nil. -func (s *IntegrationTestSuiteBase) Executor() common.Executor { +func (s *IntegrationTestSuiteBase) Executor() executor.Executor { if s.executor == nil { - s.executor = common.NewExecutor() + exec, err := executor.New() + s.Require().NoError(err) + s.executor = exec } return s.executor } @@ -164,9 +168,11 @@ func (s *IntegrationTestSuiteBase) RegisterCleanup(containers ...string) { // if resources are already gone. containers = append(containers, containerStatsName) s.cleanupContainers(containers...) + // StopCollector is safe when collector isn't running, but the container must exist. // This will ensure that logs are still written even when test setup fails - if exists, _ := s.Executor().ContainerExists("collector"); exists { + exists, _ := s.Executor().ContainerExists(executor.ContainerFilter{Name: "collector"}) + if exists { s.StopCollector() } }) @@ -258,7 +264,7 @@ func (s *IntegrationTestSuiteBase) GetLogLines(containerName string) []string { } func (s *IntegrationTestSuiteBase) launchContainer(name string, args ...string) (string, error) { - cmd := []string{common.RuntimeCommand, "run", "-d", "--name", name} + cmd := []string{executor.RuntimeCommand, "run", "-d", "--name", name} cmd = append(cmd, args...) output, err := s.Executor().Exec(cmd...) @@ -280,7 +286,7 @@ func (s *IntegrationTestSuiteBase) waitForContainerStatus( filter string) (bool, error) { cmd := []string{ - common.RuntimeCommand, "ps", "-qa", + executor.RuntimeCommand, "ps", "-qa", "--filter", "id=" + containerID, "--filter", filter, } @@ -328,7 +334,7 @@ func (s *IntegrationTestSuiteBase) findContainerHealthCheck( containerID string) (bool, error) { cmd := []string{ - common.RuntimeCommand, "inspect", "-f", + executor.RuntimeCommand, "inspect", "-f", "'{{ .Config.Healthcheck }}'", containerID, } @@ -375,21 +381,23 @@ func (s *IntegrationTestSuiteBase) waitForContainerToExit( } func (s *IntegrationTestSuiteBase) execContainer(containerName string, command []string) (string, error) { - cmd := []string{common.RuntimeCommand, "exec", containerName} + cmd := []string{executor.RuntimeCommand, "exec", containerName} cmd = append(cmd, command...) + return s.Executor().Exec(cmd...) } func (s *IntegrationTestSuiteBase) execContainerShellScript(containerName string, shell string, script string, args ...string) (string, error) { - cmd := []string{common.RuntimeCommand, "exec", "-i", containerName, shell, "-s"} + cmd := []string{executor.RuntimeCommand, "exec", "-i", containerName, shell, "-s"} cmd = append(cmd, args...) + return s.Executor().ExecWithStdin(script, cmd...) } func (s *IntegrationTestSuiteBase) cleanupContainers(containers ...string) { for _, container := range containers { s.Executor().KillContainer(container) - s.Executor().RemoveContainer(container) + s.Executor().RemoveContainer(executor.ContainerFilter{Name: container}) } } @@ -401,21 +409,35 @@ func (s *IntegrationTestSuiteBase) stopContainers(containers ...string) { func (s *IntegrationTestSuiteBase) removeContainers(containers ...string) { for _, container := range containers { - s.Executor().RemoveContainer(container) + s.Executor().RemoveContainer(executor.ContainerFilter{Name: container}) } } func (s *IntegrationTestSuiteBase) containerLogs(containerName string) (string, error) { - return s.Executor().Exec(common.RuntimeCommand, "logs", containerName) + return s.Executor().Exec(executor.RuntimeCommand, "logs", containerName) } func (s *IntegrationTestSuiteBase) getIPAddress(containerName string) (string, error) { - stdoutStderr, err := s.Executor().Exec(common.RuntimeCommand, "inspect", "--format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'", containerName) + args := []string{ + executor.RuntimeCommand, + "inspect", + "--format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'", + containerName, + } + + stdoutStderr, err := s.Executor().Exec(args...) return strings.Replace(string(stdoutStderr), "'", "", -1), err } func (s *IntegrationTestSuiteBase) getPort(containerName string) (string, error) { - stdoutStderr, err := s.Executor().Exec(common.RuntimeCommand, "inspect", "--format='{{json .NetworkSettings.Ports}}'", containerName) + args := []string{ + executor.RuntimeCommand, + "inspect", + "--format='{{json .NetworkSettings.Ports}}'", + containerName, + } + + stdoutStderr, err := s.Executor().Exec(args...) if err != nil { return "", err } @@ -468,7 +490,7 @@ func (s *IntegrationTestSuiteBase) RunCollectorBenchmark() { func (s *IntegrationTestSuiteBase) StartContainerStats() { image := config.Images().QaImageByKey("performance-stats") - args := []string{"-v", common.RuntimeSocket + ":/var/run/docker.sock", image} + args := []string{"-v", executor.RuntimeSocket + ":/var/run/docker.sock", image} err := s.Executor().PullImage(image) s.Require().NoError(err) diff --git a/integration-tests/suites/benchmark.go b/integration-tests/suites/benchmark.go index d9fbfd8a40..eefd3b0eb5 100644 --- a/integration-tests/suites/benchmark.go +++ b/integration-tests/suites/benchmark.go @@ -9,8 +9,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/stackrox/collector/integration-tests/suites/common" - "github.com/stackrox/collector/integration-tests/suites/config" + "github.com/stackrox/collector/integration-tests/pkg/common" + "github.com/stackrox/collector/integration-tests/pkg/config" ) type BenchmarkBaselineTestSuite struct { diff --git a/integration-tests/suites/connections_and_endpoints.go b/integration-tests/suites/connections_and_endpoints.go index 0adf777f54..2fd5657c06 100644 --- a/integration-tests/suites/connections_and_endpoints.go +++ b/integration-tests/suites/connections_and_endpoints.go @@ -6,9 +6,10 @@ import ( "strings" "time" - "github.com/stackrox/collector/integration-tests/suites/common" - "github.com/stackrox/collector/integration-tests/suites/config" - "github.com/stackrox/collector/integration-tests/suites/types" + "github.com/stackrox/collector/integration-tests/pkg/collector" + "github.com/stackrox/collector/integration-tests/pkg/common" + "github.com/stackrox/collector/integration-tests/pkg/config" + "github.com/stackrox/collector/integration-tests/pkg/types" "github.com/stretchr/testify/assert" ) @@ -31,7 +32,7 @@ func (s *ConnectionsAndEndpointsTestSuite) SetupSuite() { s.RegisterCleanup(s.Server.Name, s.Client.Name) s.StartContainerStats() - collectorOptions := common.CollectorStartupOptions{ + collectorOptions := collector.StartupOptions{ Env: map[string]string{ "ROX_PROCESSES_LISTENING_ON_PORT": "true", "ROX_ENABLE_AFTERGLOW": "false", diff --git a/integration-tests/suites/duplicate_endpoints.go b/integration-tests/suites/duplicate_endpoints.go index 6621bca58a..116d776a56 100644 --- a/integration-tests/suites/duplicate_endpoints.go +++ b/integration-tests/suites/duplicate_endpoints.go @@ -5,8 +5,9 @@ import ( "strings" "time" - "github.com/stackrox/collector/integration-tests/suites/common" - "github.com/stackrox/collector/integration-tests/suites/config" + "github.com/stackrox/collector/integration-tests/pkg/collector" + "github.com/stackrox/collector/integration-tests/pkg/common" + "github.com/stackrox/collector/integration-tests/pkg/config" ) const ( @@ -33,7 +34,7 @@ func (s *DuplicateEndpointsTestSuite) SetupSuite() { s.RegisterCleanup("socat") s.StartContainerStats() - collectorOptions := common.CollectorStartupOptions{ + collectorOptions := collector.StartupOptions{ Config: map[string]any{ "turnOffScrape": false, "scrapeInterval": gScrapeInterval, diff --git a/integration-tests/suites/gperftools.go b/integration-tests/suites/gperftools.go index 52ca2742b5..4e0738b19e 100644 --- a/integration-tests/suites/gperftools.go +++ b/integration-tests/suites/gperftools.go @@ -5,7 +5,7 @@ import ( "strings" "time" - "github.com/stackrox/collector/integration-tests/suites/common" + "github.com/stackrox/collector/integration-tests/pkg/common" ) type GperftoolsTestSuite struct { diff --git a/integration-tests/suites/image_json.go b/integration-tests/suites/image_json.go index ce6ab95c36..1c9bad0b4e 100644 --- a/integration-tests/suites/image_json.go +++ b/integration-tests/suites/image_json.go @@ -1,7 +1,7 @@ package suites import ( - "github.com/stackrox/collector/integration-tests/suites/config" + "github.com/stackrox/collector/integration-tests/pkg/config" ) type ImageLabelJSONTestSuite struct { diff --git a/integration-tests/suites/k8s_namespace.go b/integration-tests/suites/k8s_namespace.go new file mode 100644 index 0000000000..08943e9478 --- /dev/null +++ b/integration-tests/suites/k8s_namespace.go @@ -0,0 +1,266 @@ +package suites + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "time" + + "github.com/stackrox/collector/integration-tests/pkg/collector" + "github.com/stackrox/collector/integration-tests/pkg/executor" + + coreV1 "k8s.io/api/core/v1" + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/remotecommand" +) + +const ( + NAMESPACE = "target-namespace" +) + +type NamespaceTest struct { + containerID string + expectecNamespace string +} + +type K8sNamespaceTestSuite struct { + IntegrationTestSuiteBase + tests []NamespaceTest + collectorIP string +} + +func (k *K8sNamespaceTestSuite) SetupSuite() { + // Ensure the collector pod gets deleted + k.T().Cleanup(func() { + exists, err := k.Executor().ContainerExists(executor.ContainerFilter{ + Name: "collector", + Namespace: collector.TEST_NAMESPACE, + }) + k.Require().NoError(err) + if exists { + k.Collector().TearDown() + } + + if k.sensor != nil { + k.sensor.Stop() + } + + nginxPodFilter := executor.ContainerFilter{ + Name: "nginx", + Namespace: NAMESPACE, + } + exists, _ = k.Executor().ContainerExists(nginxPodFilter) + + if exists { + k.Executor().RemoveContainer(nginxPodFilter) + } + + k8sExecutor, ok := k.Executor().(*executor.K8sExecutor) + if !ok { + k.Require().FailNow("Incorrect executor type. got=%T, want=K8sExecutor", k.Executor()) + } + + exists, _ = k8sExecutor.NamespaceExists(NAMESPACE) + if exists { + k8sExecutor.RemoveNamespace(NAMESPACE) + } + }) + + // Start Sensor + k.Sensor().Start() + + err := k.Collector().Setup(&collector.StartupOptions{ + Env: map[string]string{ + "ROX_COLLECTOR_RUNTIME_FILTERS_ENABLED": "true", + "ROX_COLLECTOR_INTROSPECTION_ENABLE": "true", + }, + }) + k.Require().NoError(err) + err = k.Collector().Launch() + k.Require().NoError(err) + + // wait for collector to be running + k.watchPod("app=collector", collector.TEST_NAMESPACE, func() bool { + return len(k.Collector().ContainerID()) != 0 + }) + + containerID := k.Collector().ContainerID() + if len(containerID) == 0 { + k.FailNow("Failed to get collector container ID") + } + + // wait for the canary process to guarantee collector is started + selfCheckOk := k.Sensor().WaitProcessesN( + containerID, 30*time.Second, 1, func() { + // Self-check process is not going to be sent via GRPC, instead + // create at least one canary process to make sure everything is + // fine. + fmt.Printf("Spawn a canary process, container ID: %s\n", k.Collector().ContainerID()) + _, err = k.execPod("collector", collector.TEST_NAMESPACE, []string{"echo"}) + k.Require().NoError(err) + }) + k.Require().True(selfCheckOk) + + k.collectorIP = k.getCollectorIP() + k.tests = append(k.tests, NamespaceTest{ + containerID: containerID, + expectecNamespace: collector.TEST_NAMESPACE, + }) + + nginxID := k.launchNginxPod() + k.Require().Len(nginxID, 12) + k.tests = append(k.tests, NamespaceTest{ + containerID: nginxID, + expectecNamespace: NAMESPACE, + }) +} + +func (k *K8sNamespaceTestSuite) TearDownSuite() { +} + +func (k *K8sNamespaceTestSuite) TestK8sNamespace() { + baseUri := "http://" + k.collectorIP + ":8080/state/containers/" + + for _, tt := range k.tests { + uri := baseUri + tt.containerID + fmt.Printf("Querying: %s\n", uri) + resp, err := http.Get(uri) + k.Require().NoError(err) + k.Require().True(resp.StatusCode == 200) + + defer resp.Body.Close() + raw, err := io.ReadAll(resp.Body) + fmt.Printf("Response: %s\n", raw) + + var body map[string]interface{} + err = json.Unmarshal(raw, &body) + k.Require().NoError(err) + + cIdInterface, ok := body["container_id"] + k.Require().True(ok) + cId, ok := cIdInterface.(string) + k.Require().True(ok) + k.Require().Equal(cId, tt.containerID) + + namespaceInterface, ok := body["namespace"] + k.Require().True(ok) + namespace, ok := namespaceInterface.(string) + k.Require().True(ok) + k.Require().Equal(namespace, tt.expectecNamespace) + } +} + +func (k *K8sNamespaceTestSuite) execPod(podName string, namespace string, command []string) (string, error) { + k8sExec, ok := k.executor.(*executor.K8sExecutor) + if !ok { + return "", fmt.Errorf("Expected k8s executor, got=%T", k.executor) + } + + req := k8sExec.ClientSet().CoreV1().RESTClient().Post().Resource("Pods").Name(podName).Namespace(namespace).SubResource("exec") + option := &coreV1.PodExecOptions{ + Command: command, + Stdin: false, + Stdout: true, + Stderr: true, + TTY: false, + } + req.VersionedParams(option, scheme.ParameterCodec) + + config, err := rest.InClusterConfig() + if err != nil { + return "", err + } + + exec, err := remotecommand.NewSPDYExecutor(config, "POST", req.URL()) + if err != nil { + return "", err + } + + err = exec.StreamWithContext(context.Background(), remotecommand.StreamOptions{ + Stdin: nil, + Stdout: os.Stdout, + Stderr: os.Stderr, + }) + + if err != nil { + return "", err + } + + return "", nil +} + +func (k *K8sNamespaceTestSuite) watchPod(selector string, namespace string, callback func() bool) { + k8sExec := k.Executor().(*executor.K8sExecutor) + + timeout := int64(60) + listOptions := metaV1.ListOptions{ + TimeoutSeconds: &timeout, + LabelSelector: selector, + } + watcher, err := k8sExec.ClientSet().CoreV1().Pods(namespace).Watch(context.Background(), listOptions) + k.Require().NoError(err) + + for event := range watcher.ResultChan() { + switch event.Type { + case watch.Added: + case watch.Modified: + if callback() { + return + } + default: + // nothing to do here + } + } +} + +func (k *K8sNamespaceTestSuite) getCollectorIP() string { + k8sExec := k.Executor().(*executor.K8sExecutor) + pod, err := k8sExec.ClientSet().CoreV1().Pods(collector.TEST_NAMESPACE).Get(context.Background(), "collector", metaV1.GetOptions{}) + k.Require().NoError(err) + + return pod.Status.PodIP +} + +func (k *K8sNamespaceTestSuite) launchNginxPod() string { + // Create a namespace for the pod + k8sExec := k.Executor().(*executor.K8sExecutor) + + _, err := k8sExec.CreateNamespace(NAMESPACE) + + pod := &coreV1.Pod{ + ObjectMeta: metaV1.ObjectMeta{ + Name: "nginx", + Namespace: NAMESPACE, + Labels: map[string]string{ + "app": "nginx", + }, + }, + Spec: coreV1.PodSpec{ + Containers: []coreV1.Container{ + {Name: "nginx", Image: "nginx:1.24.0"}, + }, + }, + } + + _, err = k8sExec.CreatePod(NAMESPACE, pod) + k.Require().NoError(err) + + // Wait for nginx pod to start up + pf := executor.ContainerFilter{ + Name: "nginx", + Namespace: NAMESPACE, + } + + fmt.Println("Waiting for nginx to start running") + k.watchPod("app=nginx", NAMESPACE, func() bool { + return k8sExec.ContainerID(pf) != "" + }) + + return k8sExec.ContainerID(pf) +} diff --git a/integration-tests/suites/listening_ports.go b/integration-tests/suites/listening_ports.go index 95ef269432..93b7dd1db4 100644 --- a/integration-tests/suites/listening_ports.go +++ b/integration-tests/suites/listening_ports.go @@ -4,9 +4,10 @@ import ( "sort" "time" - "github.com/stackrox/collector/integration-tests/suites/common" - "github.com/stackrox/collector/integration-tests/suites/config" - "github.com/stackrox/collector/integration-tests/suites/types" + "github.com/stackrox/collector/integration-tests/pkg/collector" + "github.com/stackrox/collector/integration-tests/pkg/common" + "github.com/stackrox/collector/integration-tests/pkg/config" + "github.com/stackrox/collector/integration-tests/pkg/types" "github.com/stretchr/testify/assert" ) @@ -19,7 +20,7 @@ func (s *ProcessListeningOnPortTestSuite) SetupSuite() { s.RegisterCleanup("process-ports") s.StartContainerStats() - collectorOptions := common.CollectorStartupOptions{ + collectorOptions := collector.StartupOptions{ Config: map[string]any{ "turnOffScrape": false, "scrapeInterval": 1, diff --git a/integration-tests/suites/missing_proc_scrape.go b/integration-tests/suites/missing_proc_scrape.go index 67b14400c6..6028ac0e02 100644 --- a/integration-tests/suites/missing_proc_scrape.go +++ b/integration-tests/suites/missing_proc_scrape.go @@ -6,7 +6,7 @@ import ( "os" "path/filepath" - "github.com/stackrox/collector/integration-tests/suites/common" + "github.com/stackrox/collector/integration-tests/pkg/collector" "github.com/stretchr/testify/assert" ) @@ -63,7 +63,7 @@ func (s *MissingProcScrapeTestSuite) SetupSuite() { s.createFakeProcDir() } - collectorOptions := common.CollectorStartupOptions{ + collectorOptions := collector.StartupOptions{ Mounts: map[string]string{ "/host/proc:ro": fakeProcDir, }, diff --git a/integration-tests/suites/perf_event_open.go b/integration-tests/suites/perf_event_open.go index 957a88d956..21fe6ccc9e 100644 --- a/integration-tests/suites/perf_event_open.go +++ b/integration-tests/suites/perf_event_open.go @@ -5,7 +5,7 @@ import ( "strconv" "time" - "github.com/stackrox/collector/integration-tests/suites/config" + "github.com/stackrox/collector/integration-tests/pkg/config" "github.com/stretchr/testify/assert" ) diff --git a/integration-tests/suites/process_network.go b/integration-tests/suites/process_network.go index bf7cb75708..e45ddec006 100644 --- a/integration-tests/suites/process_network.go +++ b/integration-tests/suites/process_network.go @@ -4,9 +4,9 @@ import ( "fmt" "time" - "github.com/stackrox/collector/integration-tests/suites/common" - "github.com/stackrox/collector/integration-tests/suites/config" - "github.com/stackrox/collector/integration-tests/suites/types" + "github.com/stackrox/collector/integration-tests/pkg/common" + "github.com/stackrox/collector/integration-tests/pkg/config" + "github.com/stackrox/collector/integration-tests/pkg/types" ) type ProcessNetworkTestSuite struct { diff --git a/integration-tests/suites/processes_and_endpoints.go b/integration-tests/suites/processes_and_endpoints.go index ae906b4226..ddf6797524 100644 --- a/integration-tests/suites/processes_and_endpoints.go +++ b/integration-tests/suites/processes_and_endpoints.go @@ -3,9 +3,10 @@ package suites import ( "time" - "github.com/stackrox/collector/integration-tests/suites/common" - "github.com/stackrox/collector/integration-tests/suites/config" - "github.com/stackrox/collector/integration-tests/suites/types" + "github.com/stackrox/collector/integration-tests/pkg/collector" + "github.com/stackrox/collector/integration-tests/pkg/common" + "github.com/stackrox/collector/integration-tests/pkg/config" + "github.com/stackrox/collector/integration-tests/pkg/types" "github.com/stretchr/testify/assert" ) @@ -23,7 +24,7 @@ func (s *ProcessesAndEndpointsTestSuite) SetupSuite() { s.RegisterCleanup(s.ContainerName) s.StartContainerStats() - collectorOptions := common.CollectorStartupOptions{ + collectorOptions := collector.StartupOptions{ Env: map[string]string{ "ROX_PROCESSES_LISTENING_ON_PORT": "true", }, diff --git a/integration-tests/suites/procfs_scraper.go b/integration-tests/suites/procfs_scraper.go index 7f47580f14..329da55123 100644 --- a/integration-tests/suites/procfs_scraper.go +++ b/integration-tests/suites/procfs_scraper.go @@ -4,9 +4,10 @@ import ( "strconv" "time" - "github.com/stackrox/collector/integration-tests/suites/common" - "github.com/stackrox/collector/integration-tests/suites/config" - "github.com/stackrox/collector/integration-tests/suites/types" + "github.com/stackrox/collector/integration-tests/pkg/collector" + "github.com/stackrox/collector/integration-tests/pkg/common" + "github.com/stackrox/collector/integration-tests/pkg/config" + "github.com/stackrox/collector/integration-tests/pkg/types" ) type ProcfsScraperTestSuite struct { @@ -27,7 +28,7 @@ func (s *ProcfsScraperTestSuite) SetupSuite() { s.StartContainerStats() - collectorOptions := common.CollectorStartupOptions{ + collectorOptions := collector.StartupOptions{ Env: map[string]string{ "ROX_PROCESSES_LISTENING_ON_PORT": strconv.FormatBool(s.RoxProcessesListeningOnPort), }, diff --git a/integration-tests/suites/repeated_network_flow.go b/integration-tests/suites/repeated_network_flow.go index 229cc1179c..9b08b2a339 100644 --- a/integration-tests/suites/repeated_network_flow.go +++ b/integration-tests/suites/repeated_network_flow.go @@ -5,8 +5,8 @@ import ( "strconv" "time" - "github.com/stackrox/collector/integration-tests/suites/common" - "github.com/stackrox/collector/integration-tests/suites/config" + "github.com/stackrox/collector/integration-tests/pkg/collector" + "github.com/stackrox/collector/integration-tests/pkg/config" "github.com/stretchr/testify/assert" ) @@ -38,7 +38,7 @@ func (s *RepeatedNetworkFlowTestSuite) SetupSuite() { s.RegisterCleanup("nginx", "nginx-curl") s.StartContainerStats() - collectorOptions := common.CollectorStartupOptions{ + collectorOptions := collector.StartupOptions{ Config: map[string]any{ // turnOffScrape will be true, but the scrapeInterval // also controls the reporting interval for network events diff --git a/integration-tests/suites/socat.go b/integration-tests/suites/socat.go index cd1f4008e3..f31feda1d8 100644 --- a/integration-tests/suites/socat.go +++ b/integration-tests/suites/socat.go @@ -6,9 +6,10 @@ import ( "strconv" "time" - "github.com/stackrox/collector/integration-tests/suites/common" - "github.com/stackrox/collector/integration-tests/suites/config" - "github.com/stackrox/collector/integration-tests/suites/types" + "github.com/stackrox/collector/integration-tests/pkg/collector" + "github.com/stackrox/collector/integration-tests/pkg/common" + "github.com/stackrox/collector/integration-tests/pkg/config" + "github.com/stackrox/collector/integration-tests/pkg/types" "github.com/stretchr/testify/assert" ) @@ -32,7 +33,7 @@ func (s *SocatTestSuite) SetupSuite() { s.RegisterCleanup("socat") s.StartContainerStats() - collectorOptions := common.CollectorStartupOptions{ + collectorOptions := collector.StartupOptions{ Config: map[string]any{ "turnOffScrape": false, }, diff --git a/integration-tests/suites/symlink_process.go b/integration-tests/suites/symlink_process.go index ff72794aae..5d5597c1e9 100644 --- a/integration-tests/suites/symlink_process.go +++ b/integration-tests/suites/symlink_process.go @@ -5,8 +5,9 @@ import ( "github.com/stretchr/testify/assert" - "github.com/stackrox/collector/integration-tests/suites/common" - "github.com/stackrox/collector/integration-tests/suites/types" + "github.com/stackrox/collector/integration-tests/pkg/collector" + "github.com/stackrox/collector/integration-tests/pkg/common" + "github.com/stackrox/collector/integration-tests/pkg/types" ) type SymbolicLinkProcessTestSuite struct { @@ -18,7 +19,7 @@ func (s *SymbolicLinkProcessTestSuite) SetupSuite() { s.RegisterCleanup("process-ports") s.StartContainerStats() - collectorOptions := common.CollectorStartupOptions{ + collectorOptions := collector.StartupOptions{ Config: map[string]any{ "turnOffScrape": false, },