Compare commits
419 Commits
Author | SHA1 | Date |
---|---|---|
|
9f30cce152 | |
|
090d95418e | |
|
6a25fa9f5a | |
|
ffb6e688e8 | |
|
a7b6cc6b6b | |
|
fd12beb1cd | |
|
d3c99d5dfc | |
|
6d9b57671f | |
|
54c47d097f | |
|
7a94528218 | |
|
876dcf4653 | |
|
581cbbc316 | |
|
b26c309f4d | |
|
15dd8fde15 | |
|
abdf330e55 | |
|
2b06411214 | |
|
295c633684 | |
|
bccca00d90 | |
|
54ef1cbbf1 | |
|
464a8a3cf9 | |
|
e632903105 | |
|
48a247e521 | |
|
47ad8d0e29 | |
|
3bf89dd0c9 | |
|
bb64751b9f | |
|
1765e1d326 | |
|
a6dcad415c | |
|
cead711238 | |
|
33c01252f6 | |
|
537ebcf446 | |
|
918e88464d | |
|
4bfeb0b0b5 | |
|
dfa4c583b1 | |
|
7d5aee8bb2 | |
|
0bce031350 | |
|
84d7fc852e | |
|
a420868063 | |
|
dabadcec4f | |
|
68484cd32b | |
|
3319e217d0 | |
|
99e76506f0 | |
|
4da81510d1 | |
|
1a71698e3f | |
|
94b56ea805 | |
|
ec3025e878 | |
|
6c71d1551e | |
|
d715341096 | |
|
335e365a78 | |
|
946d8f7b05 | |
|
088e2b45ec | |
|
6bcfad674e | |
|
f9734bb752 | |
|
dc7634cc28 | |
|
e71898ce53 | |
|
8a4febabe5 | |
|
cd992ad99c | |
|
730f80f471 | |
|
18c4322102 | |
|
769a3e5683 | |
|
38f2b1d703 | |
|
ba2730f048 | |
|
b6e47e6ea5 | |
|
84edb08963 | |
|
6bcda9881d | |
|
186b281e71 | |
|
2b11bc95df | |
|
c1ac24469d | |
|
bbdf8ac2f2 | |
|
310a119170 | |
|
f222cf4174 | |
|
af1ebe264b | |
|
70dda77939 | |
|
40b9e11683 | |
|
be7d5c2a55 | |
|
6f64f37774 | |
|
9dafcec9af | |
|
6f241ae803 | |
|
5bf4186068 | |
|
98c4183dd5 | |
|
9f360e12dd | |
|
a40d97f766 | |
|
cdd984dbf2 | |
|
61d9c71d96 | |
|
5776706e3c | |
|
46a22251cd | |
|
28ccc66497 | |
|
4599fe26b8 | |
|
a06072bd76 | |
|
3967730442 | |
|
076aef28c1 | |
|
cb7fd150ba | |
|
afaa782db6 | |
|
cc97e5ebaa | |
|
c29cad76b0 | |
|
2604fe2228 | |
|
1ac62368af | |
|
063491c111 | |
|
5846155f97 | |
|
131abecc4b | |
|
d35acb1b98 | |
|
2e87813a52 | |
|
eb9e67af1f | |
|
029726ec5d | |
|
ad33f00b56 | |
|
23094128e8 | |
|
3d077a8cac | |
|
503961ae91 | |
|
b041a7c6df | |
|
c3cd34939b | |
|
fb0a65a113 | |
|
f737d5f5ac | |
|
59245fdd66 | |
|
d6b1cfbff3 | |
|
f5722b9482 | |
|
7c57d3ee69 | |
|
fac6f31c61 | |
|
133952e1a6 | |
|
1f19a08b6a | |
|
c3a8850de6 | |
|
ebafd49a81 | |
|
3662765ac3 | |
|
46a47e0ef0 | |
|
3b3b535632 | |
|
fad320efff | |
|
7a741b57a9 | |
|
11b95ab75f | |
|
b00c0bd462 | |
|
4f562a2884 | |
|
7e06ca9fe2 | |
|
0a0cd490c5 | |
|
4fce8b537b | |
|
9c510af202 | |
|
db496e591d | |
|
27d569627e | |
|
ce6e1eef36 | |
|
e0d71e7da4 | |
|
7e6b79e9bd | |
|
fcf4def293 | |
|
1632a2d4f7 | |
|
7e05ec1b73 | |
|
006301cd5a | |
|
d1a3559878 | |
|
1a5aee911f | |
|
45af0b205a | |
|
73bfdb5b56 | |
|
cfe101856a | |
|
2dcedc0511 | |
|
d2a010445f | |
|
36b951fff3 | |
|
228cb06d4f | |
|
f55a5e6a06 | |
|
fc648dcf0e | |
|
8c56a3c85c | |
|
1f8d186326 | |
|
913d8b44eb | |
|
ee92cbf49b | |
|
150ef391f5 | |
|
76be634364 | |
|
096b8eaac2 | |
|
d6dd5cdf2f | |
|
11182eb1b8 | |
|
1b6596b5f2 | |
|
dbf56c0a2c | |
|
f5ebba87f8 | |
|
cd5ac2c073 | |
|
d7d61f4a55 | |
|
eb8fbb741e | |
|
9d429de90d | |
|
f82978f817 | |
|
ab0bebd8b3 | |
|
0d663fc1c6 | |
|
219f3d7955 | |
|
6c5f43833f | |
|
72e7129727 | |
|
addd0cd577 | |
|
5cb609d1f9 | |
|
048e8a1375 | |
|
dd7b7169a4 | |
|
5af9b5290a | |
|
655f0ea1d4 | |
|
6b4ddd46eb | |
|
ae5a30be6b | |
|
da932e4bc8 | |
|
8ec7466b1c | |
|
d3f4b8c902 | |
|
6b870bc5ed | |
|
04abd653ef | |
|
8fb4c37130 | |
|
f660a9bca0 | |
|
9584f0e57a | |
|
ffbdac3d85 | |
|
352ceeb019 | |
|
ff7bc7817e | |
|
0469ee9353 | |
|
30429211f5 | |
|
7982692c6d | |
|
d0523cae9d | |
|
00d41694af | |
|
bf405ec0d1 | |
|
d772260f9b | |
|
67e2bb4e28 | |
|
2524b16440 | |
|
b98eff4e7a | |
|
cce5b80106 | |
|
64f00cd2dd | |
|
ea3fe222cc | |
|
058f959467 | |
|
913962c6cf | |
|
aaf3c4d778 | |
|
27c653f9e5 | |
|
da9865e209 | |
|
d2be4ab6b8 | |
|
c30089c970 | |
|
5bb003a24b | |
|
72bc9dd259 | |
|
d26a3eded2 | |
|
10cb99c4c9 | |
|
aefeac7221 | |
|
24c44967e8 | |
|
6de519341a | |
|
38ea1a3aea | |
|
390edc9e54 | |
|
f6b76de25c | |
|
da016311a5 | |
|
c36c065856 | |
|
f7bcb4af5c | |
|
ecf420f9f7 | |
|
5a9cc2909c | |
|
e5bc056ce6 | |
|
72decef12a | |
|
7c36297edf | |
|
363047407e | |
|
3e10f205ef | |
|
f0827ad844 | |
|
c73bac5113 | |
|
358abeb70a | |
|
75a2e12512 | |
|
5d5b16f3fd | |
|
7783ee535c | |
|
d94e19ec24 | |
|
388b8b02ba | |
|
0296002808 | |
|
4e3c510ed2 | |
|
1bda127c79 | |
|
c81c1b8c8b | |
|
1c8c697051 | |
|
a46a849389 | |
|
a1e10ab84a | |
|
92188837b5 | |
|
7a2d69e2e3 | |
|
064c201df3 | |
|
e596dea6bb | |
|
8f98b0db57 | |
|
d5539af528 | |
|
1c6ab282db | |
|
c2dead34f1 | |
|
72a5d38754 | |
|
a89aa89890 | |
|
cf958dc2d9 | |
|
7977184710 | |
|
18982d3434 | |
|
7667649a2e | |
|
8ec25c9287 | |
|
1d9d19d833 | |
|
0532e71ccd | |
|
580d49884a | |
|
46a3602fcf | |
|
1673558c55 | |
|
b6b799bc96 | |
|
477f2c3786 | |
|
d73a859e62 | |
|
fb82fec00c | |
|
82c2307d1a | |
|
d1f70386ea | |
|
e6665cd8fc | |
|
379832c738 | |
|
803c08e374 | |
|
816f79cf1e | |
|
4dd3f2a631 | |
|
c95c65af00 | |
|
be3ce60a75 | |
|
2d0a52a967 | |
|
3575a81966 | |
|
1b23e3f4f7 | |
|
9163a85547 | |
|
cf773bcfc0 | |
|
b9d5516f68 | |
|
6e39c4665f | |
|
ad38eaf8f3 | |
|
15b4ff9017 | |
|
f2a5a9060e | |
|
e005be04e7 | |
|
5d5632c5f0 | |
|
85f1edd708 | |
|
c62ffbd24f | |
|
e827ba1cee | |
|
ac15f50d35 | |
|
2ed594744c | |
|
0763d7d1bb | |
|
f5a8d65b88 | |
|
8859d1e625 | |
|
4e745c1813 | |
|
1fd04e0dbf | |
|
869fcbe642 | |
|
3337612782 | |
|
43f1d3c7f9 | |
|
5d2bfa7b49 | |
|
2b5982cd0b | |
|
6ee5a87614 | |
|
16dd7b093c | |
|
de9b39bad1 | |
|
18bae1d745 | |
|
d08fead3d3 | |
|
e03c73c33d | |
|
270f36234b | |
|
0ff99bd776 | |
|
bf23a65bcc | |
|
95104b15c6 | |
|
d3dbcbd6bb | |
|
215569116f | |
|
3eb92959c3 | |
|
0a29ccc7fd | |
|
16e2d1a45d | |
|
128916b4b4 | |
|
4f9b935aea | |
|
919a307bc2 | |
|
da51356ef5 | |
|
ef15eb2e4a | |
|
ff244b6d34 | |
|
e4ced876ba | |
|
3a8ac8e708 | |
|
bb3ee1d550 | |
|
4ec8660ed2 | |
|
139527dad6 | |
|
e87c302591 | |
|
1e299e6b48 | |
|
8717a9eeff | |
|
405e341449 | |
|
79a1b7f9f6 | |
|
478c7c6630 | |
|
d86ed68ad1 | |
|
889a6346ea | |
|
28dd5f6307 | |
|
a5f0ecd1cf | |
|
bf2aec883d | |
|
e6d75ca083 | |
|
1f079780d7 | |
|
d63deb6dcb | |
|
606672e3a3 | |
|
c14e50cc46 | |
|
c26ed79dd3 | |
|
dce407905a | |
|
72999a66dd | |
|
ca1be8ab1e | |
|
01986a74cf | |
|
58b66f5ec7 | |
|
c89a6a82eb | |
|
13434184bd | |
|
5cb7f759ed | |
|
2e95198423 | |
|
e6658f0e37 | |
|
d72536cb40 | |
|
cda38ca875 | |
|
36aa56c0a2 | |
|
ce7d0b231c | |
|
50654041ae | |
|
dc5eea97ae | |
|
f9062a7665 | |
|
c8d7485b3c | |
|
5adab46b99 | |
|
bf63125778 | |
|
0bdbd1b19d | |
|
2b21ae9f04 | |
|
913bae3010 | |
|
af100a3da8 | |
|
256e8bf4b9 | |
|
9783fd3690 | |
|
a50b7b321d | |
|
2b56ef422e | |
|
1a013c60ad | |
|
609a8a255d | |
|
d5893e1917 | |
|
7b8ef7abaa | |
|
c19ca32447 | |
|
2b217999ec | |
|
62d9da10bd | |
|
d594814c72 | |
|
313f047209 | |
|
15aa18090c | |
|
a39b5ac7c9 | |
|
610e851189 | |
|
077b671bc5 | |
|
0ae01ef387 | |
|
d762a9c6f7 | |
|
f80f3fd180 | |
|
0643ed3044 | |
|
42c069ef77 | |
|
cb049cb890 | |
|
4228e71a06 | |
|
1ad11dba9a | |
|
6d34d4045e | |
|
5cfe0575a3 | |
|
fbcb975f55 | |
|
f656760e78 | |
|
cdccbeb83e | |
|
ce1f9a8968 | |
|
f547c49766 | |
|
262029791e | |
|
b5363f6908 | |
|
bea50efc21 | |
|
ae7f565b03 | |
|
0490543b61 | |
|
05eb3df8eb | |
|
49e73bbec7 | |
|
da90565512 | |
|
b6678c2e18 | |
|
c8359f3348 | |
|
22eda6a284 | |
|
7a66862212 |
|
@ -1,6 +1,6 @@
|
||||||
<!-- Thanks for sending a pull request! Here are some tips for you:
|
<!-- Thanks for sending a pull request! Here are some tips for you:
|
||||||
|
|
||||||
1. If this is your first time, please read our contributor guidelines in the [CONTRIBUTING.md](https://github.com/falcosecurity/falco/blob/dev/CONTRIBUTING.md) file in the Falco repository.
|
1. If this is your first time, please read our contributor guidelines in the [CONTRIBUTING.md](https://github.com/falcosecurity/.github/blob/main/CONTRIBUTING.md) file in the Falco `.github` repository.
|
||||||
2. Please label this pull request according to what type of issue you are addressing.
|
2. Please label this pull request according to what type of issue you are addressing.
|
||||||
3. Please add a release note!
|
3. Please add a release note!
|
||||||
4. If the PR is unfinished while opening it specify a wip in the title before the actual title, for example, "wip: my awesome feature"
|
4. If the PR is unfinished while opening it specify a wip in the title before the actual title, for example, "wip: my awesome feature"
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: gomod
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
open-pull-requests-limit: 10
|
||||||
|
groups:
|
||||||
|
gomod:
|
||||||
|
update-types:
|
||||||
|
- "patch"
|
||||||
|
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
open-pull-requests-limit: 10
|
||||||
|
groups:
|
||||||
|
actions:
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
|
@ -26,7 +26,7 @@ jobs:
|
||||||
- go
|
- go
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@d23060145bc9131d50558d5d4185494a20208101 # v2.2.8
|
uses: github/codeql-action/init@d23060145bc9131d50558d5d4185494a20208101 # v2.2.8
|
||||||
with:
|
with:
|
||||||
|
|
|
@ -21,6 +21,10 @@ on:
|
||||||
description: The digest of the pushed image.
|
description: The digest of the pushed image.
|
||||||
value: ${{ jobs.docker-image.outputs.digest }}
|
value: ${{ jobs.docker-image.outputs.digest }}
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docker-image:
|
docker-image:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
@ -29,30 +33,43 @@ jobs:
|
||||||
digest: ${{ steps.build-and-push.outputs.digest }}
|
digest: ${{ steps.build-and-push.outputs.digest }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # v2.1.0
|
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
id: Buildx
|
id: Buildx
|
||||||
uses: docker/setup-buildx-action@4b4e9c3e2d4531116a6f8ba8e71fc6e2cb6e6c8c # v2.5.0
|
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0
|
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USER }}
|
username: ${{ secrets.DOCKERHUB_USER }}
|
||||||
password: ${{ secrets.DOCKERHUB_SECRET }}
|
password: ${{ secrets.DOCKERHUB_SECRET }}
|
||||||
|
|
||||||
|
- name: Configure AWS credentials
|
||||||
|
uses: aws-actions/configure-aws-credentials@b47578312673ae6fa5b5096b330d9fbac3d116df # v4.2.1
|
||||||
|
with:
|
||||||
|
role-to-assume: arn:aws:iam::292999226676:role/github_actions-falcoctl-ecr
|
||||||
|
aws-region: us-east-1
|
||||||
|
|
||||||
|
- name: Login to Amazon ECR
|
||||||
|
id: login-ecr-public
|
||||||
|
uses: aws-actions/amazon-ecr-login@062b18b96a7aff071d4dc91bc00c4c1a7945b076 # v2.0.1
|
||||||
|
with:
|
||||||
|
registry-type: public
|
||||||
|
|
||||||
- name: Docker Meta
|
- name: Docker Meta
|
||||||
id: meta_falcoctl
|
id: meta_falcoctl
|
||||||
uses: docker/metadata-action@507c2f2dc502c992ad446e3d7a5dfbe311567a96 # v4.3.0
|
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
|
||||||
with:
|
with:
|
||||||
# list of Docker images to use as base name for tags
|
# list of Docker images to use as base name for tags
|
||||||
images: |
|
images: |
|
||||||
docker.io/falcosecurity/falcoctl
|
docker.io/falcosecurity/falcoctl
|
||||||
|
public.ecr.aws/falcosecurity/falcoctl
|
||||||
tags: |
|
tags: |
|
||||||
type=ref,event=branch
|
type=ref,event=branch
|
||||||
type=semver,pattern={{ version }}
|
type=semver,pattern={{ version }}
|
||||||
|
@ -61,7 +78,7 @@ jobs:
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
id: build-and-push
|
id: build-and-push
|
||||||
uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671 # v4.0.0
|
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
|
@ -75,7 +92,7 @@ jobs:
|
||||||
|
|
||||||
- name: Install Cosign
|
- name: Install Cosign
|
||||||
if: ${{ inputs.sign }}
|
if: ${{ inputs.sign }}
|
||||||
uses: sigstore/cosign-installer@dd6b2e2b610a11fd73dd187a43d57cc1394e35f9 # v3.0.5
|
uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2
|
||||||
|
|
||||||
- name: Sign the images with GitHub OIDC Token
|
- name: Sign the images with GitHub OIDC Token
|
||||||
if: ${{ inputs.sign }}
|
if: ${{ inputs.sign }}
|
||||||
|
|
|
@ -23,14 +23,14 @@ jobs:
|
||||||
goos: windows
|
goos: windows
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout commit
|
- name: Checkout commit
|
||||||
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||||
with:
|
with:
|
||||||
go-version: '1.21'
|
go-version-file: 'go.mod'
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|
||||||
- name: Build Falcoctl
|
- name: Build Falcoctl
|
||||||
|
@ -47,14 +47,14 @@ jobs:
|
||||||
tar -czvf falcoctl-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz falcoctl LICENSE
|
tar -czvf falcoctl-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz falcoctl LICENSE
|
||||||
|
|
||||||
- name: Upload falcoctl artifacts
|
- name: Upload falcoctl artifacts
|
||||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: falcoctl-${{ matrix.goos }}-${{ matrix.goarch }}
|
name: falcoctl-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||||
path: ./falcoctl-${{ matrix.goos }}-${{ matrix.goarch }}
|
path: ./falcoctl-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
||||||
- name: Upload falcoctl archives
|
- name: Upload falcoctl archives
|
||||||
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: falcoctl-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz
|
name: falcoctl-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz
|
||||||
path: ./falcoctl-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz
|
path: ./falcoctl-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz
|
||||||
|
@ -80,22 +80,86 @@ jobs:
|
||||||
needs: docker-configure
|
needs: docker-configure
|
||||||
uses: ./.github/workflows/docker-image.yaml
|
uses: ./.github/workflows/docker-image.yaml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
with:
|
with:
|
||||||
release: ${{ needs.docker-configure.outputs.release }}
|
release: ${{ needs.docker-configure.outputs.release }}
|
||||||
commit: ${{ needs.docker-configure.outputs.commit }}
|
commit: ${{ needs.docker-configure.outputs.commit }}
|
||||||
build_date: ${{ needs.docker-configure.outputs.build_date }}
|
build_date: ${{ needs.docker-configure.outputs.build_date }}
|
||||||
|
sign: true
|
||||||
|
|
||||||
|
provenance-for-images-docker:
|
||||||
|
if: ${{ github.event_name == 'push' }}
|
||||||
|
needs: [docker-configure, docker-image]
|
||||||
|
permissions:
|
||||||
|
actions: read # for detecting the Github Actions environment.
|
||||||
|
id-token: write # for creating OIDC tokens for signing.
|
||||||
|
packages: write # for uploading attestations.
|
||||||
|
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
|
||||||
|
with:
|
||||||
|
image: docker.io/falcosecurity/falcoctl
|
||||||
|
# The image digest is used to prevent TOCTOU issues.
|
||||||
|
# This is an output of the docker/build-push-action
|
||||||
|
# See: https://github.com/slsa-framework/slsa-verifier#toctou-attacks
|
||||||
|
digest: ${{ needs.docker-image.outputs.digest }}
|
||||||
|
secrets:
|
||||||
|
registry-username: ${{ secrets.DOCKERHUB_USER }}
|
||||||
|
registry-password: ${{ secrets.DOCKERHUB_SECRET }}
|
||||||
|
|
||||||
|
login-to-amazon-ecr:
|
||||||
|
if: ${{ github.event_name == 'push' }}
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
|
steps:
|
||||||
|
- name: Configure AWS credentials
|
||||||
|
uses: aws-actions/configure-aws-credentials@b47578312673ae6fa5b5096b330d9fbac3d116df # v4.2.1
|
||||||
|
with:
|
||||||
|
role-to-assume: arn:aws:iam::292999226676:role/github_actions-falcoctl-ecr
|
||||||
|
aws-region: us-east-1
|
||||||
|
|
||||||
|
- name: Login to Amazon ECR
|
||||||
|
id: login-ecr-public
|
||||||
|
uses: aws-actions/amazon-ecr-login@062b18b96a7aff071d4dc91bc00c4c1a7945b076 # v2.0.1
|
||||||
|
with:
|
||||||
|
registry-type: public
|
||||||
|
mask-password: 'false'
|
||||||
|
outputs:
|
||||||
|
registry: ${{ steps.login-ecr-public.outputs.registry }}
|
||||||
|
docker_username: ${{ steps.login-ecr-public.outputs.docker_username_public_ecr_aws }}
|
||||||
|
docker_password: ${{ steps.login-ecr-public.outputs.docker_password_public_ecr_aws }}
|
||||||
|
|
||||||
|
provenance-for-images-aws-ecr:
|
||||||
|
if: ${{ github.event_name == 'push' }}
|
||||||
|
needs: [docker-configure, docker-image, login-to-amazon-ecr]
|
||||||
|
permissions:
|
||||||
|
actions: read # for detecting the Github Actions environment.
|
||||||
|
id-token: write # for creating OIDC tokens for signing.
|
||||||
|
packages: write # for uploading attestations.
|
||||||
|
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
|
||||||
|
with:
|
||||||
|
image: public.ecr.aws/falcosecurity/falcoctl
|
||||||
|
# The image digest is used to prevent TOCTOU issues.
|
||||||
|
# This is an output of the docker/build-push-action
|
||||||
|
# See: https://github.com/slsa-framework/slsa-verifier#toctou-attacks
|
||||||
|
digest: ${{ needs.docker-image.outputs.digest }}
|
||||||
|
secrets:
|
||||||
|
registry-username: ${{ needs.login-to-amazon-ecr.outputs.docker_username }}
|
||||||
|
registry-password: ${{ needs.login-to-amazon-ecr.outputs.docker_password }}
|
||||||
|
|
||||||
test:
|
test:
|
||||||
needs: build
|
needs: build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout commit
|
- name: Checkout commit
|
||||||
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||||
with:
|
with:
|
||||||
go-version: '1.21'
|
go-version-file: 'go.mod'
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
|
|
|
@ -8,24 +8,25 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||||
with:
|
with:
|
||||||
go-version: '1.21'
|
go-version: "^1.24.3"
|
||||||
|
go-version-file: "go.mod"
|
||||||
check-latest: true
|
check-latest: true
|
||||||
cache: 'false'
|
cache: "false"
|
||||||
|
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3.7.0
|
uses: golangci/golangci-lint-action@55c2c1448f86e01eaae002a5a3a9624417608d84 # v6.5.2
|
||||||
with:
|
with:
|
||||||
only-new-issues: true
|
only-new-issues: true
|
||||||
version: v1.54.2
|
version: v1.64.7
|
||||||
args: --timeout=900s
|
args: --timeout=900s
|
||||||
|
|
||||||
gomodtidy:
|
gomodtidy:
|
||||||
|
@ -34,16 +35,16 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
with:
|
with:
|
||||||
ref: "${{ github.event.pull_request.head.sha }}"
|
ref: "${{ github.event.pull_request.head.sha }}"
|
||||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||||
with:
|
with:
|
||||||
go-version: '1.21'
|
go-version-file: "go.mod"
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|
||||||
- name: Execute go mod tidy and check the outcome
|
- name: Execute go mod tidy and check the outcome
|
||||||
|
|
|
@ -14,7 +14,7 @@ jobs:
|
||||||
hashes: ${{ steps.hash.outputs.hashes }}
|
hashes: ${{ steps.hash.outputs.hashes }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
@ -22,14 +22,14 @@ jobs:
|
||||||
run: git fetch --force --tags
|
run: git fetch --force --tags
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||||
with:
|
with:
|
||||||
go-version: '1.21'
|
go-version-file: 'go.mod'
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
id: run-goreleaser
|
id: run-goreleaser
|
||||||
uses: goreleaser/goreleaser-action@f82d6c1c344bcacabba2c841718984797f664a6b # v4.2.0
|
uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0
|
||||||
with:
|
with:
|
||||||
distribution: goreleaser
|
distribution: goreleaser
|
||||||
version: latest
|
version: latest
|
||||||
|
@ -53,7 +53,7 @@ jobs:
|
||||||
actions: read # To read the workflow path.
|
actions: read # To read the workflow path.
|
||||||
id-token: write # To sign the provenance.
|
id-token: write # To sign the provenance.
|
||||||
contents: write # To add assets to a release.
|
contents: write # To add assets to a release.
|
||||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.6.0
|
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
|
||||||
with:
|
with:
|
||||||
base64-subjects: "${{ needs.goreleaser.outputs.hashes }}"
|
base64-subjects: "${{ needs.goreleaser.outputs.hashes }}"
|
||||||
upload-assets: true # upload to a new release
|
upload-assets: true # upload to a new release
|
||||||
|
@ -64,7 +64,7 @@ jobs:
|
||||||
permissions: read-all
|
permissions: read-all
|
||||||
steps:
|
steps:
|
||||||
- name: Install the verifier
|
- name: Install the verifier
|
||||||
uses: slsa-framework/slsa-verifier/actions/installer@v2.3.0
|
uses: slsa-framework/slsa-verifier/actions/installer@v2.7.1
|
||||||
|
|
||||||
- name: Download assets
|
- name: Download assets
|
||||||
env:
|
env:
|
||||||
|
@ -120,13 +120,13 @@ jobs:
|
||||||
build_date: ${{ needs.docker-configure.outputs.build_date }}
|
build_date: ${{ needs.docker-configure.outputs.build_date }}
|
||||||
sign: true
|
sign: true
|
||||||
|
|
||||||
provenance-for-images:
|
provenance-for-images-docker:
|
||||||
needs: [docker-configure, docker-image]
|
needs: [docker-configure, docker-image]
|
||||||
permissions:
|
permissions:
|
||||||
actions: read # for detecting the Github Actions environment.
|
actions: read # for detecting the Github Actions environment.
|
||||||
id-token: write # for creating OIDC tokens for signing.
|
id-token: write # for creating OIDC tokens for signing.
|
||||||
packages: write # for uploading attestations.
|
packages: write # for uploading attestations.
|
||||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.6.0
|
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
|
||||||
with:
|
with:
|
||||||
image: docker.io/falcosecurity/falcoctl
|
image: docker.io/falcosecurity/falcoctl
|
||||||
# The image digest is used to prevent TOCTOU issues.
|
# The image digest is used to prevent TOCTOU issues.
|
||||||
|
@ -136,3 +136,43 @@ jobs:
|
||||||
secrets:
|
secrets:
|
||||||
registry-username: ${{ secrets.DOCKERHUB_USER }}
|
registry-username: ${{ secrets.DOCKERHUB_USER }}
|
||||||
registry-password: ${{ secrets.DOCKERHUB_SECRET }}
|
registry-password: ${{ secrets.DOCKERHUB_SECRET }}
|
||||||
|
|
||||||
|
login-to-amazon-ecr:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
|
steps:
|
||||||
|
- name: Configure AWS credentials
|
||||||
|
uses: aws-actions/configure-aws-credentials@b47578312673ae6fa5b5096b330d9fbac3d116df # v4.2.1
|
||||||
|
with:
|
||||||
|
role-to-assume: arn:aws:iam::292999226676:role/github_actions-falcoctl-ecr
|
||||||
|
aws-region: us-east-1
|
||||||
|
|
||||||
|
- name: Login to Amazon ECR
|
||||||
|
id: login-ecr-public
|
||||||
|
uses: aws-actions/amazon-ecr-login@062b18b96a7aff071d4dc91bc00c4c1a7945b076 # v2.0.1
|
||||||
|
with:
|
||||||
|
registry-type: public
|
||||||
|
mask-password: 'false'
|
||||||
|
outputs:
|
||||||
|
registry: ${{ steps.login-ecr-public.outputs.registry }}
|
||||||
|
docker_username: ${{ steps.login-ecr-public.outputs.docker_username_public_ecr_aws }}
|
||||||
|
docker_password: ${{ steps.login-ecr-public.outputs.docker_password_public_ecr_aws }}
|
||||||
|
|
||||||
|
provenance-for-images-aws-ecr:
|
||||||
|
needs: [docker-configure, docker-image, login-to-amazon-ecr]
|
||||||
|
permissions:
|
||||||
|
actions: read # for detecting the Github Actions environment.
|
||||||
|
id-token: write # for creating OIDC tokens for signing.
|
||||||
|
packages: write # for uploading attestations.
|
||||||
|
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
|
||||||
|
with:
|
||||||
|
image: public.ecr.aws/falcosecurity/falcoctl
|
||||||
|
# The image digest is used to prevent TOCTOU issues.
|
||||||
|
# This is an output of the docker/build-push-action
|
||||||
|
# See: https://github.com/slsa-framework/slsa-verifier#toctou-attacks
|
||||||
|
digest: ${{ needs.docker-image.outputs.digest }}
|
||||||
|
secrets:
|
||||||
|
registry-username: ${{ needs.login-to-amazon-ecr.outputs.docker_username }}
|
||||||
|
registry-password: ${{ needs.login-to-amazon-ecr.outputs.docker_password }}
|
||||||
|
|
|
@ -11,7 +11,8 @@ linters-settings:
|
||||||
const:
|
const:
|
||||||
AUTHORS: The Falco Authors
|
AUTHORS: The Falco Authors
|
||||||
template: |-
|
template: |-
|
||||||
Copyright {{ YEAR }} {{ AUTHORS }}
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
Copyright (C) {{ YEAR }} {{ AUTHORS }}
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -43,17 +44,11 @@ linters-settings:
|
||||||
- opinionated
|
- opinionated
|
||||||
- performance
|
- performance
|
||||||
- style
|
- style
|
||||||
disabled-checks:
|
|
||||||
# Conflicts with govet check-shadowing
|
|
||||||
- sloppyReassign
|
|
||||||
goimports:
|
goimports:
|
||||||
local-prefixes: github.com/falcosecurity/falcoctl
|
local-prefixes: github.com/falcosecurity/falcoctl
|
||||||
govet:
|
|
||||||
check-shadowing: true
|
|
||||||
misspell:
|
misspell:
|
||||||
locale: US
|
locale: US
|
||||||
nolintlint:
|
nolintlint:
|
||||||
allow-leading-space: true # don't require machine-readable nolint directives (i.e. with no leading space)
|
|
||||||
allow-unused: false # report any unused nolint directives
|
allow-unused: false # report any unused nolint directives
|
||||||
require-explanation: true # require an explanation for nolint directives
|
require-explanation: true # require an explanation for nolint directives
|
||||||
require-specific: true # require nolint directives to be specific about which linter is being skipped
|
require-specific: true # require nolint directives to be specific about which linter is being skipped
|
||||||
|
@ -70,7 +65,7 @@ linters:
|
||||||
- errcheck
|
- errcheck
|
||||||
- errorlint
|
- errorlint
|
||||||
- exhaustive
|
- exhaustive
|
||||||
- exportloopref
|
- copyloopvar
|
||||||
# - funlen
|
# - funlen
|
||||||
# - gochecknoglobals
|
# - gochecknoglobals
|
||||||
# - gochecknoinits
|
# - gochecknoinits
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
version: 2
|
||||||
|
|
||||||
project_name: falcoctl
|
project_name: falcoctl
|
||||||
before:
|
before:
|
||||||
hooks:
|
hooks:
|
||||||
|
@ -16,6 +18,8 @@ builds:
|
||||||
ignore:
|
ignore:
|
||||||
- goos: darwin
|
- goos: darwin
|
||||||
goarch: 386
|
goarch: 386
|
||||||
|
- goos: windows
|
||||||
|
goarch: 386
|
||||||
|
|
||||||
ldflags: |
|
ldflags: |
|
||||||
-X github.com/falcosecurity/falcoctl/cmd/version.buildDate={{ .Date }}
|
-X github.com/falcosecurity/falcoctl/cmd/version.buildDate={{ .Date }}
|
||||||
|
@ -43,3 +47,6 @@ release:
|
||||||
|
|
||||||
changelog:
|
changelog:
|
||||||
use: github-native
|
use: github-native
|
||||||
|
|
||||||
|
git:
|
||||||
|
tag_sort: -version:creatordate
|
||||||
|
|
9
Makefile
9
Makefile
|
@ -18,6 +18,7 @@ PROJECT?=github.com/falcosecurity/falcoctl
|
||||||
# todo(leogr): re-enable race when CLI tests can run with race enabled
|
# todo(leogr): re-enable race when CLI tests can run with race enabled
|
||||||
TEST_FLAGS ?= -v -cover# -race
|
TEST_FLAGS ?= -v -cover# -race
|
||||||
|
|
||||||
|
.PHONY: falcoctl
|
||||||
falcoctl:
|
falcoctl:
|
||||||
$(GO) build -ldflags \
|
$(GO) build -ldflags \
|
||||||
"-X '${PROJECT}/cmd/version.semVersion=${RELEASE}' \
|
"-X '${PROJECT}/cmd/version.semVersion=${RELEASE}' \
|
||||||
|
@ -34,7 +35,7 @@ test:
|
||||||
.PHONY: gci
|
.PHONY: gci
|
||||||
gci:
|
gci:
|
||||||
ifeq (, $(shell which gci))
|
ifeq (, $(shell which gci))
|
||||||
@go install github.com/daixiang0/gci@v0.9.0
|
@go install github.com/daixiang0/gci@v0.11.1
|
||||||
GCI=$(GOBIN)/gci
|
GCI=$(GOBIN)/gci
|
||||||
else
|
else
|
||||||
GCI=$(shell which gci)
|
GCI=$(shell which gci)
|
||||||
|
@ -56,13 +57,13 @@ fmt: gci addlicense
|
||||||
go mod tidy
|
go mod tidy
|
||||||
go fmt ./...
|
go fmt ./...
|
||||||
find . -type f -name '*.go' -a -exec $(GCI) write -s standard -s default -s "prefix(github.com/falcosecurity/falcoctl)" {} \;
|
find . -type f -name '*.go' -a -exec $(GCI) write -s standard -s default -s "prefix(github.com/falcosecurity/falcoctl)" {} \;
|
||||||
find . -type f -name '*.go' -exec $(ADDLICENSE) -l apache -c "The Falco Authors" -y "$(shell date +%Y)" {} \;
|
find . -type f -name '*.go' -exec $(ADDLICENSE) -l apache -s -c "The Falco Authors" -y "$(shell date +%Y)" {} \;
|
||||||
|
|
||||||
# Install golangci-lint if not available
|
# Install golangci-lint if not available
|
||||||
.PHONY: golangci-lint
|
.PHONY: golangci-lint
|
||||||
golangci-lint:
|
golangci-lint:
|
||||||
ifeq (, $(shell which golangci-lint))
|
ifeq (, $(shell which golangci-lint))
|
||||||
@go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.52.2
|
@go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2
|
||||||
GOLANGCILINT=$(GOBIN)/golangci-lint
|
GOLANGCILINT=$(GOBIN)/golangci-lint
|
||||||
else
|
else
|
||||||
GOLANGCILINT=$(shell which golangci-lint)
|
GOLANGCILINT=$(shell which golangci-lint)
|
||||||
|
@ -80,4 +81,4 @@ docker:
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
@rm falcoctl
|
@rm falcoctl || true
|
||||||
|
|
1
OWNERS
1
OWNERS
|
@ -4,7 +4,6 @@ approvers:
|
||||||
- maxgio92
|
- maxgio92
|
||||||
- fededp
|
- fededp
|
||||||
- cpanato
|
- cpanato
|
||||||
reviewers:
|
|
||||||
- alacuku
|
- alacuku
|
||||||
- loresuso
|
- loresuso
|
||||||
emeritus_approvers:
|
emeritus_approvers:
|
||||||
|
|
12
README.md
12
README.md
|
@ -23,6 +23,13 @@ sudo install -o root -g root -m 0755 falcoctl /usr/local/bin/falcoctl
|
||||||
> NOTE: Make sure */usr/local/bin* is in your PATH environment variable.
|
> NOTE: Make sure */usr/local/bin* is in your PATH environment variable.
|
||||||
|
|
||||||
#### MacOS
|
#### MacOS
|
||||||
|
The easiest way to install on MacOS is via `Homebrew`:
|
||||||
|
```bash
|
||||||
|
brew install falcoctl
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, you can download directly from the source:
|
||||||
|
|
||||||
##### Intel
|
##### Intel
|
||||||
```bash
|
```bash
|
||||||
LATEST=$(curl -sI https://github.com/falcosecurity/falcoctl/releases/latest | awk '/location: /{gsub("\r","",$2);split($2,v,"/");print substr(v[8],2)}')
|
LATEST=$(curl -sI https://github.com/falcosecurity/falcoctl/releases/latest | awk '/location: /{gsub("\r","",$2);split($2,v,"/");print substr(v[8],2)}')
|
||||||
|
@ -209,6 +216,8 @@ Indices for *falcoctl* can be retrieved from various storage backends. The suppo
|
||||||
| http | http:// | Can be used to retrieve indices via simple HTTP GET requests. |
|
| http | http:// | Can be used to retrieve indices via simple HTTP GET requests. |
|
||||||
| https | https:// | Convenience alias for the HTTP backend. |
|
| https | https:// | Convenience alias for the HTTP backend. |
|
||||||
| gcs | gs:// | For indices stored as Google Cloud Storage objects. Supports application default credentials. |
|
| gcs | gs:// | For indices stored as Google Cloud Storage objects. Supports application default credentials. |
|
||||||
|
| file | file:// | For indices stored on the local file system. |
|
||||||
|
| s3 | s3:// | For indices stored as AWS S3 objects. Supports default credentials, IRSA. |
|
||||||
|
|
||||||
|
|
||||||
#### falcoctl index add
|
#### falcoctl index add
|
||||||
|
@ -335,10 +344,11 @@ $ falcoctl registry push --type=plugin ghcr.io/falcosecurity/plugins/plugin/clou
|
||||||
```
|
```
|
||||||
The type denotes the **artifact** type in this case *plugins*. The `ghcr.io/falcosecurity/plugins/plugin/cloudtrail:0.3.0` is the unique reference that points to the **artifact**.
|
The type denotes the **artifact** type in this case *plugins*. The `ghcr.io/falcosecurity/plugins/plugin/cloudtrail:0.3.0` is the unique reference that points to the **artifact**.
|
||||||
Currently, *falcoctl* supports only two types of artifacts: **plugin** and **rulesfile**. Based on **artifact type** the commands accepts different flags:
|
Currently, *falcoctl* supports only two types of artifacts: **plugin** and **rulesfile**. Based on **artifact type** the commands accepts different flags:
|
||||||
|
* `--add-floating-tags`: add the floating tags for the major and minor versions
|
||||||
* `--annotation-source`: set annotation source for the artifact;
|
* `--annotation-source`: set annotation source for the artifact;
|
||||||
* `--depends-on`: set an artifact dependency (can be specified multiple times). Example: `--depends-on my-plugin:1.2.3`
|
* `--depends-on`: set an artifact dependency (can be specified multiple times). Example: `--depends-on my-plugin:1.2.3`
|
||||||
* `--tag`: additional artifact tag. Can be repeated multiple time
|
* `--tag`: additional artifact tag. Can be repeated multiple time
|
||||||
* `--type`: type of artifact to be pushed. Allowed values: `rulesfile`, `plugin`
|
* `--type`: type of artifact to be pushed. Allowed values: `rulesfile`, `plugin`, `asset`
|
||||||
|
|
||||||
### Falcoctl registry pull
|
### Falcoctl registry pull
|
||||||
Pulling **artifacts** involves specifying the reference. The type of **artifact** is not required since the tool will implicitly extract it from the OCI **artifact**:
|
Pulling **artifacts** involves specifying the reference. The type of **artifact** is not required since the tool will implicitly extract it from the OCI **artifact**:
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM golang:1.21 as builder
|
FROM cgr.dev/chainguard/go AS builder
|
||||||
WORKDIR /tmp/builder
|
WORKDIR /tmp/builder
|
||||||
|
|
||||||
ARG RELEASE
|
ARG RELEASE
|
||||||
|
@ -29,12 +29,8 @@ RUN CGO_ENABLED=0 \
|
||||||
|
|
||||||
RUN echo ${RELEASE}
|
RUN echo ${RELEASE}
|
||||||
|
|
||||||
FROM alpine:3.18.3
|
FROM cgr.dev/chainguard/static:latest
|
||||||
|
|
||||||
RUN rm -rf /var/cache/apk/*
|
COPY --from=builder /tmp/builder/falcoctl /usr/bin/falcoctl
|
||||||
|
|
||||||
ARG BIN_NAME="falcoctl"
|
ENTRYPOINT [ "/usr/bin/falcoctl" ]
|
||||||
COPY --from=builder /tmp/builder/${BIN_NAME} /usr/bin/${BIN_NAME}
|
|
||||||
RUN ln -s /usr/bin/${BIN_NAME} /usr/bin/falcoctl-bin
|
|
||||||
|
|
||||||
ENTRYPOINT [ "/usr/bin/falcoctl-bin" ]
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2022 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -19,10 +20,12 @@ import (
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
artifactconfig "github.com/falcosecurity/falcoctl/cmd/artifact/config"
|
||||||
"github.com/falcosecurity/falcoctl/cmd/artifact/follow"
|
"github.com/falcosecurity/falcoctl/cmd/artifact/follow"
|
||||||
"github.com/falcosecurity/falcoctl/cmd/artifact/info"
|
"github.com/falcosecurity/falcoctl/cmd/artifact/info"
|
||||||
"github.com/falcosecurity/falcoctl/cmd/artifact/install"
|
"github.com/falcosecurity/falcoctl/cmd/artifact/install"
|
||||||
"github.com/falcosecurity/falcoctl/cmd/artifact/list"
|
"github.com/falcosecurity/falcoctl/cmd/artifact/list"
|
||||||
|
"github.com/falcosecurity/falcoctl/cmd/artifact/manifest"
|
||||||
"github.com/falcosecurity/falcoctl/cmd/artifact/search"
|
"github.com/falcosecurity/falcoctl/cmd/artifact/search"
|
||||||
"github.com/falcosecurity/falcoctl/internal/config"
|
"github.com/falcosecurity/falcoctl/internal/config"
|
||||||
"github.com/falcosecurity/falcoctl/pkg/index/cache"
|
"github.com/falcosecurity/falcoctl/pkg/index/cache"
|
||||||
|
@ -36,8 +39,6 @@ func NewArtifactCmd(ctx context.Context, opt *commonoptions.Common) *cobra.Comma
|
||||||
DisableFlagsInUseLine: true,
|
DisableFlagsInUseLine: true,
|
||||||
Short: "Interact with Falco artifacts",
|
Short: "Interact with Falco artifacts",
|
||||||
Long: "Interact with Falco artifacts",
|
Long: "Interact with Falco artifacts",
|
||||||
SilenceErrors: true,
|
|
||||||
SilenceUsage: true,
|
|
||||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
var indexes []config.Index
|
var indexes []config.Index
|
||||||
var indexCache *cache.Cache
|
var indexCache *cache.Cache
|
||||||
|
@ -70,6 +71,8 @@ func NewArtifactCmd(ctx context.Context, opt *commonoptions.Common) *cobra.Comma
|
||||||
cmd.AddCommand(list.NewArtifactListCmd(ctx, opt))
|
cmd.AddCommand(list.NewArtifactListCmd(ctx, opt))
|
||||||
cmd.AddCommand(info.NewArtifactInfoCmd(ctx, opt))
|
cmd.AddCommand(info.NewArtifactInfoCmd(ctx, opt))
|
||||||
cmd.AddCommand(follow.NewArtifactFollowCmd(ctx, opt))
|
cmd.AddCommand(follow.NewArtifactFollowCmd(ctx, opt))
|
||||||
|
cmd.AddCommand(artifactconfig.NewArtifactConfigCmd(ctx, opt))
|
||||||
|
cmd.AddCommand(manifest.NewArtifactManifestCmd(ctx, opt))
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
ocipuller "github.com/falcosecurity/falcoctl/pkg/oci/puller"
|
||||||
|
ociutils "github.com/falcosecurity/falcoctl/pkg/oci/utils"
|
||||||
|
"github.com/falcosecurity/falcoctl/pkg/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
type artifactConfigOptions struct {
|
||||||
|
*options.Common
|
||||||
|
*options.Registry
|
||||||
|
platform string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewArtifactConfigCmd returns the artifact config command.
|
||||||
|
func NewArtifactConfigCmd(ctx context.Context, opt *options.Common) *cobra.Command {
|
||||||
|
o := artifactConfigOptions{
|
||||||
|
Common: opt,
|
||||||
|
Registry: &options.Registry{},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "config [ref] [flags]",
|
||||||
|
Short: "Get the config layer of an artifact",
|
||||||
|
Long: "Get the config layer of an artifact",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return o.RunArtifactConfig(ctx, args)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
o.Registry.AddFlags(cmd)
|
||||||
|
cmd.Flags().StringVar(&o.platform, "platform", fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
|
||||||
|
"os and architecture of the artifact in OS/ARCH format")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *artifactConfigOptions) RunArtifactConfig(ctx context.Context, args []string) error {
|
||||||
|
var (
|
||||||
|
puller *ocipuller.Puller
|
||||||
|
ref string
|
||||||
|
config []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create puller with auto login enabled.
|
||||||
|
if puller, err = ociutils.Puller(o.PlainHTTP, o.Printer); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve the artifact reference.
|
||||||
|
if ref, err = o.IndexCache.ResolveReference(args[0]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: implement two new flags (platforms, platform) based on the oci platform struct.
|
||||||
|
// Split the platform.
|
||||||
|
tokens := strings.Split(o.platform, "/")
|
||||||
|
if len(tokens) != 2 {
|
||||||
|
return fmt.Errorf("invalid platform format: %s", o.platform)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config, err = puller.RawConfigLayer(ctx, ref, tokens[0], tokens[1]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
o.Printer.DefaultText.Println(string(config))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,142 @@
|
||||||
|
//SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package config_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/distribution/distribution/v3/configuration"
|
||||||
|
_ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/onsi/gomega/gbytes"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"oras.land/oras-go/v2/registry/remote"
|
||||||
|
"oras.land/oras-go/v2/registry/remote/auth"
|
||||||
|
|
||||||
|
"github.com/falcosecurity/falcoctl/cmd"
|
||||||
|
"github.com/falcosecurity/falcoctl/pkg/oci"
|
||||||
|
"github.com/falcosecurity/falcoctl/pkg/oci/authn"
|
||||||
|
ocipusher "github.com/falcosecurity/falcoctl/pkg/oci/pusher"
|
||||||
|
commonoptions "github.com/falcosecurity/falcoctl/pkg/options"
|
||||||
|
testutils "github.com/falcosecurity/falcoctl/pkg/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
localRegistryHost string
|
||||||
|
localRegistry *remote.Registry
|
||||||
|
testRuleTarball = "../../../pkg/test/data/rules.tar.gz"
|
||||||
|
testPluginTarball = "../../../pkg/test/data/plugin.tar.gz"
|
||||||
|
testPluginPlatform1 = "linux/amd64"
|
||||||
|
testPluginPlatform2 = "windows/amd64"
|
||||||
|
testPluginPlatform3 = "linux/arm64"
|
||||||
|
ctx = context.Background()
|
||||||
|
pluginMultiPlatformRef string
|
||||||
|
rulesRef string
|
||||||
|
artifactWithoutConfigRef string
|
||||||
|
output = gbytes.NewBuffer()
|
||||||
|
rootCmd *cobra.Command
|
||||||
|
opt *commonoptions.Common
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfig(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Config Suite")
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = BeforeSuite(func() {
|
||||||
|
var err error
|
||||||
|
config := &configuration.Configuration{}
|
||||||
|
// Get a free port to be used by the registry.
|
||||||
|
port, err := testutils.FreePort()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
// Create the registry address to which will bind.
|
||||||
|
config.HTTP.Addr = fmt.Sprintf("localhost:%d", port)
|
||||||
|
localRegistryHost = config.HTTP.Addr
|
||||||
|
|
||||||
|
// Create the oras registry.
|
||||||
|
localRegistry, err = testutils.NewOrasRegistry(localRegistryHost, true)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
// Start the local registry.
|
||||||
|
go func() {
|
||||||
|
err := testutils.StartRegistry(context.Background(), config)
|
||||||
|
Expect(err).ToNot(BeNil())
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Check that the registry is up and accepting connections.
|
||||||
|
Eventually(func(g Gomega) error {
|
||||||
|
res, err := http.Get(fmt.Sprintf("http://%s", config.HTTP.Addr))
|
||||||
|
g.Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
g.Expect(res.StatusCode).Should(Equal(http.StatusOK))
|
||||||
|
return err
|
||||||
|
}).WithTimeout(time.Second * 5).ShouldNot(HaveOccurred())
|
||||||
|
|
||||||
|
// Initialize options for command.
|
||||||
|
opt = commonoptions.NewOptions()
|
||||||
|
opt.Initialize(commonoptions.WithWriter(output))
|
||||||
|
|
||||||
|
// Push the artifacts to the registry.
|
||||||
|
// Same artifacts will be used to test the puller code.
|
||||||
|
pusher := ocipusher.NewPusher(authn.NewClient(authn.WithCredentials(&auth.EmptyCredential)), true, nil)
|
||||||
|
|
||||||
|
// Push plugin artifact with multiple architectures.
|
||||||
|
filePathsAndPlatforms := ocipusher.WithFilepathsAndPlatforms([]string{testPluginTarball, testPluginTarball, testPluginTarball},
|
||||||
|
[]string{testPluginPlatform1, testPluginPlatform2, testPluginPlatform3})
|
||||||
|
pluginMultiPlatformRef = localRegistryHost + "/plugins:multiplatform"
|
||||||
|
artConfig := oci.ArtifactConfig{}
|
||||||
|
Expect(artConfig.ParseDependencies("my-dep:1.2.3|my-alt-dep:1.4.5")).ToNot(HaveOccurred())
|
||||||
|
Expect(artConfig.ParseRequirements("my-req:7.8.9")).ToNot(HaveOccurred())
|
||||||
|
artifactConfig := ocipusher.WithArtifactConfig(artConfig)
|
||||||
|
|
||||||
|
// Build options slice.
|
||||||
|
options := []ocipusher.Option{filePathsAndPlatforms, artifactConfig}
|
||||||
|
|
||||||
|
// Push the plugin artifact.
|
||||||
|
_, err = pusher.Push(ctx, oci.Plugin, pluginMultiPlatformRef, options...)
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
|
||||||
|
// Prepare and push artifact without config layer.
|
||||||
|
filePaths := ocipusher.WithFilepaths([]string{testRuleTarball})
|
||||||
|
artConfig = oci.ArtifactConfig{}
|
||||||
|
Expect(artConfig.ParseDependencies("dep1:1.2.3", "dep2:2.3.1")).ToNot(HaveOccurred())
|
||||||
|
options = []ocipusher.Option{
|
||||||
|
filePaths,
|
||||||
|
ocipusher.WithTags("latest"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push artifact without config layer.
|
||||||
|
// Push artifact without config layer.
|
||||||
|
artifactWithoutConfigRef = localRegistryHost + "/artifact:noconfig"
|
||||||
|
_, err = pusher.Push(ctx, oci.Rulesfile, artifactWithoutConfigRef, options...)
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
|
||||||
|
// Push a rulesfile artifact
|
||||||
|
options = append(options, ocipusher.WithArtifactConfig(artConfig))
|
||||||
|
rulesRef = localRegistryHost + "/rulesfiles:regular"
|
||||||
|
_, err = pusher.Push(ctx, oci.Rulesfile, rulesRef, options...)
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
func executeRoot(args []string) error {
|
||||||
|
rootCmd.SetArgs(args)
|
||||||
|
rootCmd.SetOut(output)
|
||||||
|
return cmd.Execute(rootCmd, opt)
|
||||||
|
}
|
|
@ -0,0 +1,213 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package config_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/onsi/gomega/gbytes"
|
||||||
|
|
||||||
|
"github.com/falcosecurity/falcoctl/cmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
var usage = `Usage:
|
||||||
|
falcoctl artifact config [ref] [flags]
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-h, --help help for config
|
||||||
|
--plain-http allows interacting with remote registry via plain http requests
|
||||||
|
|
||||||
|
Global Flags:
|
||||||
|
--config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml")
|
||||||
|
--log-format string Set formatting for logs (color, text, json) (default "color")
|
||||||
|
--log-level string Set level for logs (info, warn, debug, trace) (default "info")
|
||||||
|
`
|
||||||
|
|
||||||
|
var help = `Get the config layer of an artifact
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
falcoctl artifact config [ref] [flags]
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-h, --help help for config
|
||||||
|
--plain-http allows interacting with remote registry via plain http requests
|
||||||
|
--platform string os and architecture of the artifact in OS/ARCH format (default "linux/amd64")
|
||||||
|
|
||||||
|
Global Flags:
|
||||||
|
--config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml")
|
||||||
|
--log-format string Set formatting for logs (color, text, json) (default "color")
|
||||||
|
--log-level string Set level for logs (info, warn, debug, trace) (default "info")
|
||||||
|
`
|
||||||
|
|
||||||
|
var _ = Describe("Config", func() {
|
||||||
|
const (
|
||||||
|
artifactCmd = "artifact"
|
||||||
|
configCmd = "config"
|
||||||
|
plaingHTTP = "--plain-http"
|
||||||
|
configFlag = "--config"
|
||||||
|
platformFlag = "--platform"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
args []string
|
||||||
|
configDir string
|
||||||
|
)
|
||||||
|
|
||||||
|
var assertFailedBehavior = func(usage, specificError string) {
|
||||||
|
It("check that fails and the usage is not printed", func() {
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(usage)))
|
||||||
|
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(specificError)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
JustBeforeEach(func() {
|
||||||
|
configDir = GinkgoT().TempDir()
|
||||||
|
rootCmd = cmd.New(ctx, opt)
|
||||||
|
err = executeRoot(args)
|
||||||
|
})
|
||||||
|
|
||||||
|
JustAfterEach(func() {
|
||||||
|
err = nil
|
||||||
|
Expect(output.Clear()).ShouldNot(HaveOccurred())
|
||||||
|
args = nil
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("help message", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{artifactCmd, configCmd, "--help"}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should match the saved one", func() {
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
Expect(string(output.Contents())).Should(Equal(help))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("wrong number of arguments", func() {
|
||||||
|
When("number of arguments equal to 0", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{artifactCmd, configCmd}
|
||||||
|
})
|
||||||
|
|
||||||
|
assertFailedBehavior(usage, "ERROR accepts 1 arg(s), received 0 ")
|
||||||
|
})
|
||||||
|
|
||||||
|
When("number of arguments equal to 2", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{artifactCmd, configCmd, "arg1", "arg2", configFlag, configDir}
|
||||||
|
})
|
||||||
|
|
||||||
|
assertFailedBehavior(usage, "ERROR accepts 1 arg(s), received 2 ")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("failure", func() {
|
||||||
|
When("unreachable/non existing registry", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{artifactCmd, configCmd, "noregistry/noartifact", plaingHTTP, configFlag, configDir}
|
||||||
|
})
|
||||||
|
|
||||||
|
assertFailedBehavior(usage, "ERROR unable to get manifest: unable to fetch reference")
|
||||||
|
})
|
||||||
|
|
||||||
|
When("non existing repository", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{artifactCmd, configCmd, localRegistryHost + "/noartifact", plaingHTTP, configFlag, configDir}
|
||||||
|
})
|
||||||
|
|
||||||
|
assertFailedBehavior(usage, "noartifact:latest: not found")
|
||||||
|
})
|
||||||
|
|
||||||
|
When("non parsable reference", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{artifactCmd, configCmd, " ", plaingHTTP, configFlag, configDir}
|
||||||
|
})
|
||||||
|
|
||||||
|
assertFailedBehavior(usage, "ERROR cannot find among the configured indexes, skipping ")
|
||||||
|
})
|
||||||
|
|
||||||
|
When("no manifest for given platform", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{artifactCmd, configCmd, pluginMultiPlatformRef, plaingHTTP, configFlag, configDir, platformFlag, "linux/wrong"}
|
||||||
|
})
|
||||||
|
assertFailedBehavior(usage, "ERROR unable to get manifest: unable to find a manifest matching the given platform: linux/wrong")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("success", func() {
|
||||||
|
When("empty config layer", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{artifactCmd, configCmd, artifactWithoutConfigRef, plaingHTTP, configFlag, configDir}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should success", func() {
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
Expect(output).Should(gbytes.Say(regexp.QuoteMeta("{}")))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
When("with valid config layer", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{artifactCmd, configCmd, rulesRef, plaingHTTP, configFlag, configDir}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should success", func() {
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(`{"dependencies":[{"name":"dep1","version":"1.2.3"},{"name":"dep2","version":"2.3.1"}]}`)))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
When("no platform flag", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{artifactCmd, configCmd, pluginMultiPlatformRef, plaingHTTP, configFlag, configDir}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should success getting the platform where tests are running", func() {
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(
|
||||||
|
`{"dependencies":[{"name":"my-dep","version":"1.2.3","alternatives":[{"name":"my-alt-dep","version":"`)))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
When("with valid platform", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{artifactCmd, configCmd, pluginMultiPlatformRef, plaingHTTP, configFlag, configDir, platformFlag, testPluginPlatform3}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should success", func() {
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(
|
||||||
|
`{"dependencies":[{"name":"my-dep","version":"1.2.3","alternatives":[{"name":"my-alt-dep","version":"`)))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
When("with non existing platform for artifacts without platforms", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{artifactCmd, configCmd, rulesRef, plaingHTTP, configFlag, configDir, platformFlag, testPluginPlatform3}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should success", func() {
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(`{"dependencies":[{"name":"dep1","version":"1.2.3"},{"name":"dep2","version":"2.3.1"}]}`)))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,17 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package config defines the business logic to fetch config layer for artifacts.
|
||||||
|
package config
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2022 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2022 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2023 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -80,8 +81,7 @@ Example - Install and follow "cloudtrail" plugins using a fully qualified refere
|
||||||
type artifactFollowOptions struct {
|
type artifactFollowOptions struct {
|
||||||
*options.Common
|
*options.Common
|
||||||
*options.Registry
|
*options.Registry
|
||||||
rulesfilesDir string
|
*options.Directory
|
||||||
pluginsDir string
|
|
||||||
tmpDir string
|
tmpDir string
|
||||||
every time.Duration
|
every time.Duration
|
||||||
cron string
|
cron string
|
||||||
|
@ -100,6 +100,7 @@ func NewArtifactFollowCmd(ctx context.Context, opt *options.Common) *cobra.Comma
|
||||||
o := artifactFollowOptions{
|
o := artifactFollowOptions{
|
||||||
Common: opt,
|
Common: opt,
|
||||||
Registry: &options.Registry{},
|
Registry: &options.Registry{},
|
||||||
|
Directory: &options.Directory{},
|
||||||
closeChan: make(chan bool),
|
closeChan: make(chan bool),
|
||||||
versions: config.FalcoVersions{},
|
versions: config.FalcoVersions{},
|
||||||
}
|
}
|
||||||
|
@ -108,8 +109,6 @@ func NewArtifactFollowCmd(ctx context.Context, opt *options.Common) *cobra.Comma
|
||||||
Use: "follow [ref1 [ref2 ...]] [flags]",
|
Use: "follow [ref1 [ref2 ...]] [flags]",
|
||||||
Short: "Install a list of artifacts and continuously checks if there are updates",
|
Short: "Install a list of artifacts and continuously checks if there are updates",
|
||||||
Long: longFollow,
|
Long: longFollow,
|
||||||
SilenceErrors: true,
|
|
||||||
SilenceUsage: true,
|
|
||||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
// Override "every" flag with viper config if not set by user.
|
// Override "every" flag with viper config if not set by user.
|
||||||
f := cmd.Flags().Lookup("every")
|
f := cmd.Flags().Lookup("every")
|
||||||
|
@ -148,26 +147,38 @@ func NewArtifactFollowCmd(ctx context.Context, opt *options.Common) *cobra.Comma
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override "rulesfiles-dir" flag with viper config if not set by user.
|
// Override "rulesfiles-dir" flag with viper config if not set by user.
|
||||||
f = cmd.Flags().Lookup(install.FlagRulesFilesDir)
|
f = cmd.Flags().Lookup(options.FlagRulesFilesDir)
|
||||||
if f == nil {
|
if f == nil {
|
||||||
// should never happen
|
// should never happen
|
||||||
return fmt.Errorf("unable to retrieve flag %q", install.FlagRulesFilesDir)
|
return fmt.Errorf("unable to retrieve flag %q", options.FlagRulesFilesDir)
|
||||||
} else if !f.Changed && viper.IsSet(config.ArtifactFollowRulesfilesDirKey) {
|
} else if !f.Changed && viper.IsSet(config.ArtifactFollowRulesfilesDirKey) {
|
||||||
val := viper.Get(config.ArtifactFollowRulesfilesDirKey)
|
val := viper.Get(config.ArtifactFollowRulesfilesDirKey)
|
||||||
if err := cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val)); err != nil {
|
if err := cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val)); err != nil {
|
||||||
return fmt.Errorf("unable to overwrite %q flag: %w", install.FlagRulesFilesDir, err)
|
return fmt.Errorf("unable to overwrite %q flag: %w", options.FlagRulesFilesDir, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override "plugins-dir" flag with viper config if not set by user.
|
// Override "plugins-dir" flag with viper config if not set by user.
|
||||||
f = cmd.Flags().Lookup(install.FlagPluginsFilesDir)
|
f = cmd.Flags().Lookup(options.FlagPluginsFilesDir)
|
||||||
if f == nil {
|
if f == nil {
|
||||||
// should never happen
|
// should never happen
|
||||||
return fmt.Errorf("unable to retrieve flag %q", install.FlagPluginsFilesDir)
|
return fmt.Errorf("unable to retrieve flag %q", options.FlagPluginsFilesDir)
|
||||||
} else if !f.Changed && viper.IsSet(config.ArtifactFollowPluginsDirKey) {
|
} else if !f.Changed && viper.IsSet(config.ArtifactFollowPluginsDirKey) {
|
||||||
val := viper.Get(config.ArtifactFollowPluginsDirKey)
|
val := viper.Get(config.ArtifactFollowPluginsDirKey)
|
||||||
if err := cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val)); err != nil {
|
if err := cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val)); err != nil {
|
||||||
return fmt.Errorf("unable to overwrite %q flag: %w", install.FlagPluginsFilesDir, err)
|
return fmt.Errorf("unable to overwrite %q flag: %w", options.FlagPluginsFilesDir, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override "assets-dir" flag with viper config if not set by user.
|
||||||
|
f = cmd.Flags().Lookup(options.FlagAssetsFilesDir)
|
||||||
|
if f == nil {
|
||||||
|
// should never happen
|
||||||
|
return fmt.Errorf("unable to retrieve flag %q", options.FlagAssetsFilesDir)
|
||||||
|
} else if !f.Changed && viper.IsSet(config.ArtifactFollowAssetsDirKey) {
|
||||||
|
val := viper.Get(config.ArtifactFollowAssetsDirKey)
|
||||||
|
if err := cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val)); err != nil {
|
||||||
|
return fmt.Errorf("unable to overwrite %q flag: %w", options.FlagAssetsFilesDir, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,15 +234,11 @@ func NewArtifactFollowCmd(ctx context.Context, opt *options.Common) *cobra.Comma
|
||||||
}
|
}
|
||||||
|
|
||||||
o.Registry.AddFlags(cmd)
|
o.Registry.AddFlags(cmd)
|
||||||
|
o.Directory.AddFlags(cmd)
|
||||||
cmd.Flags().DurationVarP(&o.every, "every", "e", config.FollowResync, "Time interval how often it checks for a new version of the "+
|
cmd.Flags().DurationVarP(&o.every, "every", "e", config.FollowResync, "Time interval how often it checks for a new version of the "+
|
||||||
"artifact. Cannot be used together with 'cron' option.")
|
"artifact. Cannot be used together with 'cron' option.")
|
||||||
cmd.Flags().StringVar(&o.cron, "cron", "", "Cron-like string to specify interval how often it checks for a new version of the artifact."+
|
cmd.Flags().StringVar(&o.cron, "cron", "", "Cron-like string to specify interval how often it checks for a new version of the artifact."+
|
||||||
" Cannot be used together with 'every' option.")
|
" Cannot be used together with 'every' option.")
|
||||||
// TODO (alacuku): move it in a dedicate data structure since they are in common with artifactInstall command.
|
|
||||||
cmd.Flags().StringVarP(&o.rulesfilesDir, install.FlagRulesFilesDir, "", config.RulesfilesDir,
|
|
||||||
"Directory where to install rules")
|
|
||||||
cmd.Flags().StringVarP(&o.pluginsDir, install.FlagPluginsFilesDir, "", config.PluginsDir,
|
|
||||||
"Directory where to install plugins")
|
|
||||||
cmd.Flags().StringVar(&o.tmpDir, "tmp-dir", "", "Directory where to save temporary files")
|
cmd.Flags().StringVar(&o.tmpDir, "tmp-dir", "", "Directory where to save temporary files")
|
||||||
cmd.Flags().StringVar(&o.falcoVersions, "falco-versions", "http://localhost:8765/versions",
|
cmd.Flags().StringVar(&o.falcoVersions, "falco-versions", "http://localhost:8765/versions",
|
||||||
"Where to retrieve versions, it can be either an URL or a path to a file")
|
"Where to retrieve versions, it can be either an URL or a path to a file")
|
||||||
|
@ -252,6 +259,7 @@ Examples:
|
||||||
|
|
||||||
// RunArtifactFollow executes the business logic for the artifact follow command.
|
// RunArtifactFollow executes the business logic for the artifact follow command.
|
||||||
func (o *artifactFollowOptions) RunArtifactFollow(ctx context.Context, args []string) error {
|
func (o *artifactFollowOptions) RunArtifactFollow(ctx context.Context, args []string) error {
|
||||||
|
logger := o.Printer.Logger
|
||||||
// Retrieve configuration for follower
|
// Retrieve configuration for follower
|
||||||
configuredFollower, err := config.Follower()
|
configuredFollower, err := config.Follower()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -277,15 +285,13 @@ func (o *artifactFollowOptions) RunArtifactFollow(ctx context.Context, args []st
|
||||||
}
|
}
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
// Disable styling
|
|
||||||
o.Printer.DisableStylingf()
|
|
||||||
// For each artifact create a follower.
|
// For each artifact create a follower.
|
||||||
var followers = make(map[string]*follower.Follower, 0)
|
var followers = make(map[string]*follower.Follower, 0)
|
||||||
for _, a := range args {
|
for _, a := range args {
|
||||||
if o.cron != "" {
|
if o.cron != "" {
|
||||||
o.Printer.Info.Printfln("Creating follower for %q, with check using cron %s", a, o.cron)
|
logger.Info("Creating follower", logger.Args("artifact", a, "cron", o.cron))
|
||||||
} else {
|
} else {
|
||||||
o.Printer.Info.Printfln("Creating follower for %q, with check every %s", a, o.every.String())
|
logger.Info("Creating follower", logger.Args("artifact", a, "check every", o.every.String()))
|
||||||
}
|
}
|
||||||
ref, err := o.IndexCache.ResolveReference(a)
|
ref, err := o.IndexCache.ResolveReference(a)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -300,11 +306,11 @@ func (o *artifactFollowOptions) RunArtifactFollow(ctx context.Context, args []st
|
||||||
cfg := &follower.Config{
|
cfg := &follower.Config{
|
||||||
WaitGroup: &wg,
|
WaitGroup: &wg,
|
||||||
Resync: sched,
|
Resync: sched,
|
||||||
RulesfilesDir: o.rulesfilesDir,
|
RulesfilesDir: o.RulesfilesDir,
|
||||||
PluginsDir: o.pluginsDir,
|
PluginsDir: o.PluginsDir,
|
||||||
|
AssetsDir: o.AssetsDir,
|
||||||
ArtifactReference: ref,
|
ArtifactReference: ref,
|
||||||
PlainHTTP: o.PlainHTTP,
|
PlainHTTP: o.PlainHTTP,
|
||||||
Verbose: o.IsVerbose(),
|
|
||||||
CloseChan: o.closeChan,
|
CloseChan: o.closeChan,
|
||||||
TmpDir: o.tmpDir,
|
TmpDir: o.tmpDir,
|
||||||
FalcoVersions: o.versions,
|
FalcoVersions: o.versions,
|
||||||
|
@ -318,11 +324,9 @@ func (o *artifactFollowOptions) RunArtifactFollow(ctx context.Context, args []st
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
followers[ref] = fol
|
followers[ref] = fol
|
||||||
}
|
}
|
||||||
// Enable styling
|
|
||||||
o.Printer.EnableStyling()
|
|
||||||
|
|
||||||
for k, f := range followers {
|
for k, f := range followers {
|
||||||
o.Printer.Info.Printfln("Starting follower for %q", k)
|
logger.Info("Starting follower", logger.Args("artifact", k))
|
||||||
go f.Follow(ctx)
|
go f.Follow(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -330,7 +334,7 @@ func (o *artifactFollowOptions) RunArtifactFollow(ctx context.Context, args []st
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
|
|
||||||
// We are done, shutdown the followers.
|
// We are done, shutdown the followers.
|
||||||
o.Printer.DefaultText.Printfln("closing followers...")
|
logger.Info("Closing followers...")
|
||||||
close(o.closeChan)
|
close(o.closeChan)
|
||||||
|
|
||||||
// Wait for the followers to shutdown or that the timer expires.
|
// Wait for the followers to shutdown or that the timer expires.
|
||||||
|
@ -343,9 +347,9 @@ func (o *artifactFollowOptions) RunArtifactFollow(ctx context.Context, args []st
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-doneChan:
|
case <-doneChan:
|
||||||
o.Printer.DefaultText.Printfln("followers correctly stopped.")
|
logger.Info("Followers correctly stopped.")
|
||||||
case <-time.After(timeout):
|
case <-time.After(timeout):
|
||||||
o.Printer.DefaultText.Printfln("Timed out waiting for followers to exit")
|
logger.Info("Timed out waiting for followers to exit")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -432,11 +436,11 @@ type backoffTransport struct {
|
||||||
func (bt *backoffTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
func (bt *backoffTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
var err error
|
var err error
|
||||||
var resp *http.Response
|
var resp *http.Response
|
||||||
|
logger := bt.Printer.Logger
|
||||||
bt.startTime = time.Now()
|
bt.startTime = time.Now()
|
||||||
bt.attempts = 0
|
bt.attempts = 0
|
||||||
|
|
||||||
bt.Printer.Verbosef("Retrieving versions from Falco (timeout %s) ...", bt.Config.MaxDelay)
|
logger.Debug(fmt.Sprintf("Retrieving versions from Falco (timeout %s) ...", bt.Config.MaxDelay))
|
||||||
|
|
||||||
for {
|
for {
|
||||||
resp, err = bt.Base.RoundTrip(req)
|
resp, err = bt.Base.RoundTrip(req)
|
||||||
|
@ -451,10 +455,10 @@ func (bt *backoffTransport) RoundTrip(req *http.Request) (*http.Response, error)
|
||||||
return resp, fmt.Errorf("timeout occurred while retrieving versions from Falco")
|
return resp, fmt.Errorf("timeout occurred while retrieving versions from Falco")
|
||||||
}
|
}
|
||||||
|
|
||||||
bt.Printer.Verbosef("error: %s. Trying again in %s", err.Error(), sleep.String())
|
logger.Debug(fmt.Sprintf("error: %s. Trying again in %s", err.Error(), sleep.String()))
|
||||||
time.Sleep(sleep)
|
time.Sleep(sleep)
|
||||||
} else {
|
} else {
|
||||||
bt.Printer.Verbosef("Successfully retrieved versions from Falco ...")
|
logger.Debug("Successfully retrieved versions from Falco")
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2022 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2022 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -47,8 +48,6 @@ func NewArtifactInfoCmd(ctx context.Context, opt *options.Common) *cobra.Command
|
||||||
Short: "Retrieve all available versions of a given artifact",
|
Short: "Retrieve all available versions of a given artifact",
|
||||||
Long: "Retrieve all available versions of a given artifact",
|
Long: "Retrieve all available versions of a given artifact",
|
||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.MinimumNArgs(1),
|
||||||
SilenceErrors: true,
|
|
||||||
SilenceUsage: true,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return o.RunArtifactInfo(ctx, args)
|
return o.RunArtifactInfo(ctx, args)
|
||||||
},
|
},
|
||||||
|
@ -61,6 +60,7 @@ func NewArtifactInfoCmd(ctx context.Context, opt *options.Common) *cobra.Command
|
||||||
|
|
||||||
func (o *artifactInfoOptions) RunArtifactInfo(ctx context.Context, args []string) error {
|
func (o *artifactInfoOptions) RunArtifactInfo(ctx context.Context, args []string) error {
|
||||||
var data [][]string
|
var data [][]string
|
||||||
|
logger := o.Printer.Logger
|
||||||
|
|
||||||
client, err := ociutils.Client(true)
|
client, err := ociutils.Client(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -74,7 +74,7 @@ func (o *artifactInfoOptions) RunArtifactInfo(ctx context.Context, args []string
|
||||||
if err != nil {
|
if err != nil {
|
||||||
entry, ok := o.IndexCache.MergedIndexes.EntryByName(name)
|
entry, ok := o.IndexCache.MergedIndexes.EntryByName(name)
|
||||||
if !ok {
|
if !ok {
|
||||||
o.Printer.Warning.Printfln("cannot find %q, skipping", name)
|
logger.Warn("Cannot find artifact, skipping", logger.Args("name", name))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ref = fmt.Sprintf("%s/%s", entry.Registry, entry.Repository)
|
ref = fmt.Sprintf("%s/%s", entry.Registry, entry.Repository)
|
||||||
|
@ -92,14 +92,14 @@ func (o *artifactInfoOptions) RunArtifactInfo(ctx context.Context, args []string
|
||||||
|
|
||||||
tags, err := repo.Tags(ctx)
|
tags, err := repo.Tags(ctx)
|
||||||
if err != nil && !errors.Is(err, context.Canceled) {
|
if err != nil && !errors.Is(err, context.Canceled) {
|
||||||
o.Printer.Warning.Printfln("cannot retrieve tags from t %q, %v", ref, err)
|
logger.Warn("Cannot retrieve tags from", logger.Args("ref", ref, "reason", err.Error()))
|
||||||
continue
|
continue
|
||||||
} else if errors.Is(err, context.Canceled) {
|
} else if errors.Is(err, context.Canceled) {
|
||||||
// When the context is canceled we exit, since we receive a termination signal.
|
// When the context is canceled we exit, since we receive a termination signal.
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
joinedTags := strings.Join(tags, ", ")
|
joinedTags := strings.Join(filterOutSigTags(tags), ", ")
|
||||||
data = append(data, []string{ref, joinedTags})
|
data = append(data, []string{ref, joinedTags})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,3 +110,14 @@ func (o *artifactInfoOptions) RunArtifactInfo(ctx context.Context, args []string
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func filterOutSigTags(tags []string) []string {
|
||||||
|
// Iterate the slice in reverse to avoid index shifting when deleting
|
||||||
|
for i := len(tags) - 1; i >= 0; i-- {
|
||||||
|
if strings.HasSuffix(tags[i], ".sig") {
|
||||||
|
// Remove the element at index i by slicing the slice
|
||||||
|
tags = append(tags[:i], tags[i+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tags
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2023 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -15,16 +16,12 @@
|
||||||
package install
|
package install
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
||||||
// FlagRulesFilesDir is the name of the flag to specify the directory path of the rules files.
|
|
||||||
FlagRulesFilesDir = "rulesfiles-dir"
|
|
||||||
|
|
||||||
// FlagPluginsFilesDir is the name of the flag to specify the directory path of the plugins.
|
|
||||||
FlagPluginsFilesDir = "plugins-dir"
|
|
||||||
|
|
||||||
// FlagAllowedTypes is the name of the flag to specify allowed artifact types.
|
// FlagAllowedTypes is the name of the flag to specify allowed artifact types.
|
||||||
FlagAllowedTypes = "allowed-types"
|
FlagAllowedTypes = "allowed-types"
|
||||||
|
|
||||||
|
// FlagPlatform is the name of the flag to override the platform.
|
||||||
|
FlagPlatform = "platform"
|
||||||
|
|
||||||
// FlagResolveDeps is the name of the flag to enable artifact dependencies resolution.
|
// FlagResolveDeps is the name of the flag to enable artifact dependencies resolution.
|
||||||
FlagResolveDeps = "resolve-deps"
|
FlagResolveDeps = "resolve-deps"
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2023 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2023 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2022 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2022 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -20,6 +21,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
@ -71,9 +73,11 @@ Example - Install "cloudtrail" plugins using a fully qualified reference:
|
||||||
type artifactInstallOptions struct {
|
type artifactInstallOptions struct {
|
||||||
*options.Common
|
*options.Common
|
||||||
*options.Registry
|
*options.Registry
|
||||||
rulesfilesDir string
|
*options.Directory
|
||||||
pluginsDir string
|
|
||||||
allowedTypes oci.ArtifactTypeSlice
|
allowedTypes oci.ArtifactTypeSlice
|
||||||
|
platform string // Raw string from command line
|
||||||
|
platformArch string // Architecture portion of parsed platform string
|
||||||
|
platformOS string // OS portion of parsed platform string
|
||||||
resolveDeps bool
|
resolveDeps bool
|
||||||
noVerify bool
|
noVerify bool
|
||||||
}
|
}
|
||||||
|
@ -83,6 +87,7 @@ func NewArtifactInstallCmd(ctx context.Context, opt *options.Common) *cobra.Comm
|
||||||
o := artifactInstallOptions{
|
o := artifactInstallOptions{
|
||||||
Common: opt,
|
Common: opt,
|
||||||
Registry: &options.Registry{},
|
Registry: &options.Registry{},
|
||||||
|
Directory: &options.Directory{},
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -90,30 +95,40 @@ func NewArtifactInstallCmd(ctx context.Context, opt *options.Common) *cobra.Comm
|
||||||
DisableFlagsInUseLine: true,
|
DisableFlagsInUseLine: true,
|
||||||
Short: "Install a list of artifacts",
|
Short: "Install a list of artifacts",
|
||||||
Long: longInstall,
|
Long: longInstall,
|
||||||
SilenceErrors: true,
|
|
||||||
SilenceUsage: true,
|
|
||||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
// Override "rulesfiles-dir" flag with viper config if not set by user.
|
// Override "rulesfiles-dir" flag with viper config if not set by user.
|
||||||
f := cmd.Flags().Lookup(FlagRulesFilesDir)
|
f := cmd.Flags().Lookup(options.FlagRulesFilesDir)
|
||||||
if f == nil {
|
if f == nil {
|
||||||
// should never happen
|
// should never happen
|
||||||
return fmt.Errorf("unable to retrieve flag %q", FlagRulesFilesDir)
|
return fmt.Errorf("unable to retrieve flag %q", options.FlagRulesFilesDir)
|
||||||
} else if !f.Changed && viper.IsSet(config.ArtifactInstallRulesfilesDirKey) {
|
} else if !f.Changed && viper.IsSet(config.ArtifactInstallRulesfilesDirKey) {
|
||||||
val := viper.Get(config.ArtifactInstallRulesfilesDirKey)
|
val := viper.Get(config.ArtifactInstallRulesfilesDirKey)
|
||||||
if err := cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val)); err != nil {
|
if err := cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val)); err != nil {
|
||||||
return fmt.Errorf("unable to overwrite %q flag: %w", FlagRulesFilesDir, err)
|
return fmt.Errorf("unable to overwrite %q flag: %w", options.FlagRulesFilesDir, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override "plugins-dir" flag with viper config if not set by user.
|
// Override "plugins-dir" flag with viper config if not set by user.
|
||||||
f = cmd.Flags().Lookup(FlagPluginsFilesDir)
|
f = cmd.Flags().Lookup(options.FlagPluginsFilesDir)
|
||||||
if f == nil {
|
if f == nil {
|
||||||
// should never happen
|
// should never happen
|
||||||
return fmt.Errorf("unable to retrieve flag %q", FlagPluginsFilesDir)
|
return fmt.Errorf("unable to retrieve flag %q", options.FlagPluginsFilesDir)
|
||||||
} else if !f.Changed && viper.IsSet(config.ArtifactInstallPluginsDirKey) {
|
} else if !f.Changed && viper.IsSet(config.ArtifactInstallPluginsDirKey) {
|
||||||
val := viper.Get(config.ArtifactInstallPluginsDirKey)
|
val := viper.Get(config.ArtifactInstallPluginsDirKey)
|
||||||
if err := cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val)); err != nil {
|
if err := cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val)); err != nil {
|
||||||
return fmt.Errorf("unable to overwrite %q flag: %w", FlagPluginsFilesDir, err)
|
return fmt.Errorf("unable to overwrite %q flag: %w", options.FlagPluginsFilesDir, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override "assets-dir" flag with viper config if not set by user.
|
||||||
|
f = cmd.Flags().Lookup(options.FlagAssetsFilesDir)
|
||||||
|
if f == nil {
|
||||||
|
// should never happen
|
||||||
|
return fmt.Errorf("unable to retrieve flag %q", options.FlagAssetsFilesDir)
|
||||||
|
} else if !f.Changed && viper.IsSet(config.ArtifactFollowAssetsDirKey) {
|
||||||
|
val := viper.Get(config.ArtifactFollowAssetsDirKey)
|
||||||
|
if err := cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val)); err != nil {
|
||||||
|
return fmt.Errorf("unable to overwrite %q flag: %w", options.FlagAssetsFilesDir, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,6 +169,15 @@ func NewArtifactInstallCmd(ctx context.Context, opt *options.Common) *cobra.Comm
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse "platform" into OS and Arch
|
||||||
|
if len(o.platform) > 0 {
|
||||||
|
parts := strings.Split(o.platform, "/")
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return fmt.Errorf("invalid %q: must be in the format OS/Arch", FlagPlatform)
|
||||||
|
}
|
||||||
|
o.platformOS, o.platformArch = parts[0], parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
@ -162,16 +186,15 @@ func NewArtifactInstallCmd(ctx context.Context, opt *options.Common) *cobra.Comm
|
||||||
}
|
}
|
||||||
|
|
||||||
o.Registry.AddFlags(cmd)
|
o.Registry.AddFlags(cmd)
|
||||||
cmd.Flags().StringVarP(&o.rulesfilesDir, FlagRulesFilesDir, "", config.RulesfilesDir,
|
o.Directory.AddFlags(cmd)
|
||||||
"directory where to install rules.")
|
|
||||||
cmd.Flags().StringVarP(&o.pluginsDir, FlagPluginsFilesDir, "", config.PluginsDir,
|
|
||||||
"directory where to install plugins.")
|
|
||||||
cmd.Flags().Var(&o.allowedTypes, FlagAllowedTypes,
|
cmd.Flags().Var(&o.allowedTypes, FlagAllowedTypes,
|
||||||
fmt.Sprintf(`list of artifact types that can be installed. If not specified or configured, all types are allowed.
|
fmt.Sprintf(`list of artifact types that can be installed. If not specified or configured, all types are allowed.
|
||||||
It accepts comma separated values or it can be repeated multiple times.
|
It accepts comma separated values or it can be repeated multiple times.
|
||||||
Examples:
|
Examples:
|
||||||
--%s="rulesfile,plugin"
|
--%s="rulesfile,plugin"
|
||||||
--%s=rulesfile --%s=plugin`, FlagAllowedTypes, FlagAllowedTypes, FlagAllowedTypes))
|
--%s=rulesfile --%s=plugin`, FlagAllowedTypes, FlagAllowedTypes, FlagAllowedTypes))
|
||||||
|
cmd.Flags().StringVar(&o.platform, "platform", fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
|
||||||
|
"os and architecture of the artifact in OS/ARCH format")
|
||||||
cmd.Flags().BoolVar(&o.resolveDeps, FlagResolveDeps, true,
|
cmd.Flags().BoolVar(&o.resolveDeps, FlagResolveDeps, true,
|
||||||
"whether this command should resolve dependencies or not")
|
"whether this command should resolve dependencies or not")
|
||||||
cmd.Flags().BoolVar(&o.noVerify, FlagNoVerify, false,
|
cmd.Flags().BoolVar(&o.noVerify, FlagNoVerify, false,
|
||||||
|
@ -182,6 +205,7 @@ Examples:
|
||||||
|
|
||||||
// RunArtifactInstall executes the business logic for the artifact install command.
|
// RunArtifactInstall executes the business logic for the artifact install command.
|
||||||
func (o *artifactInstallOptions) RunArtifactInstall(ctx context.Context, args []string) error {
|
func (o *artifactInstallOptions) RunArtifactInstall(ctx context.Context, args []string) error {
|
||||||
|
logger := o.Printer.Logger
|
||||||
// Retrieve configuration for installer
|
// Retrieve configuration for installer
|
||||||
configuredInstaller, err := config.Installer()
|
configuredInstaller, err := config.Installer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -216,7 +240,7 @@ func (o *artifactInstallOptions) RunArtifactInstall(ctx context.Context, args []
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
artifactConfig, err := puller.PullConfigLayer(ctx, ref)
|
artifactConfig, err := puller.ArtifactConfig(ctx, ref, o.platformOS, o.platformArch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -243,7 +267,7 @@ func (o *artifactInstallOptions) RunArtifactInstall(ctx context.Context, args []
|
||||||
var refs []string
|
var refs []string
|
||||||
if o.resolveDeps {
|
if o.resolveDeps {
|
||||||
// Solve dependencies
|
// Solve dependencies
|
||||||
o.Printer.Info.Println("Resolving dependencies ...")
|
logger.Info("Resolving dependencies ...")
|
||||||
refs, err = ResolveDeps(resolver, args...)
|
refs, err = ResolveDeps(resolver, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -252,34 +276,36 @@ func (o *artifactInstallOptions) RunArtifactInstall(ctx context.Context, args []
|
||||||
refs = args
|
refs = args
|
||||||
}
|
}
|
||||||
|
|
||||||
o.Printer.Info.Printfln("Installing the following artifacts: %v", refs)
|
logger.Info("Installing artifacts", logger.Args("refs", refs))
|
||||||
|
|
||||||
for _, ref := range refs {
|
for _, ref := range refs {
|
||||||
ref, err = o.IndexCache.ResolveReference(ref)
|
resolvedRef, err := o.IndexCache.ResolveReference(ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
o.Printer.Info.Printfln("Preparing to pull %q", ref)
|
if signatures[resolvedRef] == nil {
|
||||||
|
if sig := o.IndexCache.SignatureForIndexRef(ref); sig != nil {
|
||||||
|
signatures[resolvedRef] = sig
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := puller.CheckAllowedType(ctx, ref, o.allowedTypes.Types); err != nil {
|
logger.Info("Preparing to pull artifact", logger.Args("ref", resolvedRef))
|
||||||
|
|
||||||
|
if err := puller.CheckAllowedType(ctx, resolvedRef, o.platformOS, o.platformArch, o.allowedTypes.Types); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install will always install artifact for the current OS and architecture
|
// Install will always install artifact for the current OS and architecture
|
||||||
result, err := puller.Pull(ctx, ref, tmpDir, runtime.GOOS, runtime.GOARCH)
|
result, err := puller.Pull(ctx, resolvedRef, tmpDir, o.platformOS, o.platformArch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
sig, ok := signatures[ref]
|
sig := signatures[resolvedRef]
|
||||||
if !ok {
|
|
||||||
// try to get the signature from the index
|
|
||||||
sig = o.IndexCache.SignatureForIndexRef(ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
if sig != nil && !o.noVerify {
|
if sig != nil && !o.noVerify {
|
||||||
repo, err := utils.RepositoryFromRef(ref)
|
repo, err := utils.RepositoryFromRef(resolvedRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -289,20 +315,24 @@ func (o *artifactInstallOptions) RunArtifactInstall(ctx context.Context, args []
|
||||||
// the exact digest that we just pulled, even if the tag gets overwritten in the meantime.
|
// the exact digest that we just pulled, even if the tag gets overwritten in the meantime.
|
||||||
digestRef := fmt.Sprintf("%s@%s", repo, result.RootDigest)
|
digestRef := fmt.Sprintf("%s@%s", repo, result.RootDigest)
|
||||||
|
|
||||||
o.Printer.Info.Printfln("Verifying signature for %s", digestRef)
|
logger.Info("Verifying signature for artifact", logger.Args("digest", digestRef))
|
||||||
err = signature.Verify(ctx, digestRef, sig)
|
err = signature.Verify(ctx, digestRef, sig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error while verifying signature for %s: %w", digestRef, err)
|
return fmt.Errorf("error while verifying signature for %s: %w", digestRef, err)
|
||||||
}
|
}
|
||||||
o.Printer.Info.Printfln("Signature successfully verified!")
|
logger.Info("Signature successfully verified!")
|
||||||
}
|
}
|
||||||
|
|
||||||
var destDir string
|
var destDir string
|
||||||
switch result.Type {
|
switch result.Type {
|
||||||
case oci.Plugin:
|
case oci.Plugin:
|
||||||
destDir = o.pluginsDir
|
destDir = o.PluginsDir
|
||||||
case oci.Rulesfile:
|
case oci.Rulesfile:
|
||||||
destDir = o.rulesfilesDir
|
destDir = o.RulesfilesDir
|
||||||
|
case oci.Asset:
|
||||||
|
destDir = o.AssetsDir
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unrecognized result type %q while pulling artifact", result.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if directory exists and is writable.
|
// Check if directory exists and is writable.
|
||||||
|
@ -311,16 +341,20 @@ func (o *artifactInstallOptions) RunArtifactInstall(ctx context.Context, args []
|
||||||
return fmt.Errorf("cannot use directory %q as install destination: %w", destDir, err)
|
return fmt.Errorf("cannot use directory %q as install destination: %w", destDir, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sp, _ := o.Printer.Spinner.Start(fmt.Sprintf("INFO: Extracting and installing %q %q", result.Type, result.Filename))
|
logger.Info("Extracting and installing artifact", logger.Args("type", result.Type, "file", result.Filename))
|
||||||
|
|
||||||
|
if !o.Printer.DisableStyling {
|
||||||
|
o.Printer.Spinner, _ = o.Printer.Spinner.Start("Extracting and installing")
|
||||||
|
}
|
||||||
|
|
||||||
result.Filename = filepath.Join(tmpDir, result.Filename)
|
result.Filename = filepath.Join(tmpDir, result.Filename)
|
||||||
|
|
||||||
f, err := os.Open(result.Filename)
|
f, err := os.Open(result.Filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract artifact and move it to its destination directory
|
// Extract artifact and move it to its destination directory
|
||||||
_, err = utils.ExtractTarGz(f, destDir)
|
_, err = utils.ExtractTarGz(ctx, f, destDir, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot extract %q to %q: %w", result.Filename, destDir, err)
|
return fmt.Errorf("cannot extract %q to %q: %w", result.Filename, destDir, err)
|
||||||
}
|
}
|
||||||
|
@ -330,7 +364,10 @@ func (o *artifactInstallOptions) RunArtifactInstall(ctx context.Context, args []
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
sp.Success(fmt.Sprintf("Artifact successfully installed in %q", destDir))
|
if o.Printer.Spinner != nil {
|
||||||
|
_ = o.Printer.Spinner.Stop()
|
||||||
|
}
|
||||||
|
logger.Info("Artifact successfully installed", logger.Args("name", resolvedRef, "type", result.Type, "digest", result.Digest, "directory", destDir))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2023 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -17,9 +18,11 @@ package install_test
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/distribution/distribution/v3/configuration"
|
"github.com/distribution/distribution/v3/configuration"
|
||||||
_ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
|
_ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
|
||||||
|
@ -70,7 +73,6 @@ var _ = BeforeSuite(func() {
|
||||||
// Create and configure the common options.
|
// Create and configure the common options.
|
||||||
opt = commonoptions.NewOptions()
|
opt = commonoptions.NewOptions()
|
||||||
opt.Initialize(commonoptions.WithWriter(output))
|
opt.Initialize(commonoptions.WithWriter(output))
|
||||||
opt.Printer.DisableStylingf()
|
|
||||||
|
|
||||||
// Create the oras registry.
|
// Create the oras registry.
|
||||||
orasRegistry, err = testutils.NewOrasRegistry(registry, true)
|
orasRegistry, err = testutils.NewOrasRegistry(registry, true)
|
||||||
|
@ -82,6 +84,14 @@ var _ = BeforeSuite(func() {
|
||||||
Expect(err).ToNot(BeNil())
|
Expect(err).ToNot(BeNil())
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// Check that the registry is up and accepting connections.
|
||||||
|
Eventually(func(g Gomega) error {
|
||||||
|
res, err := http.Get(fmt.Sprintf("http://%s", config.HTTP.Addr))
|
||||||
|
g.Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
g.Expect(res.StatusCode).Should(Equal(http.StatusOK))
|
||||||
|
return err
|
||||||
|
}).WithTimeout(time.Second * 5).ShouldNot(HaveOccurred())
|
||||||
|
|
||||||
// Create temporary directory used to save the configuration file.
|
// Create temporary directory used to save the configuration file.
|
||||||
configFile, err = testutils.CreateEmptyFile("falcoctl.yaml")
|
configFile, err = testutils.CreateEmptyFile("falcoctl.yaml")
|
||||||
Expect(err).Should(Succeed())
|
Expect(err).Should(Succeed())
|
||||||
|
@ -97,5 +107,5 @@ var _ = AfterSuite(func() {
|
||||||
func executeRoot(args []string) error {
|
func executeRoot(args []string) error {
|
||||||
rootCmd.SetArgs(args)
|
rootCmd.SetArgs(args)
|
||||||
rootCmd.SetOut(output)
|
rootCmd.SetOut(output)
|
||||||
return cmd.Execute(rootCmd, opt.Printer)
|
return cmd.Execute(rootCmd, opt)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2023 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -44,14 +45,15 @@ Flags:
|
||||||
--allowed-types=rulesfile --allowed-types=plugin
|
--allowed-types=rulesfile --allowed-types=plugin
|
||||||
-h, --help help for install
|
-h, --help help for install
|
||||||
--plain-http allows interacting with remote registry via plain http requests
|
--plain-http allows interacting with remote registry via plain http requests
|
||||||
|
--platform string os and architecture of the artifact in OS/ARCH format (default "linux/amd64")
|
||||||
--plugins-dir string directory where to install plugins. (default "/usr/share/falco/plugins")
|
--plugins-dir string directory where to install plugins. (default "/usr/share/falco/plugins")
|
||||||
--resolve-deps whether this command should resolve dependencies or not (default true)
|
--resolve-deps whether this command should resolve dependencies or not (default true)
|
||||||
--rulesfiles-dir string directory where to install rules. (default "/etc/falco")
|
--rulesfiles-dir string directory where to install rules. (default "/etc/falco")
|
||||||
|
|
||||||
Global Flags:
|
Global Flags:
|
||||||
--config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml")
|
--config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml")
|
||||||
--disable-styling Disable output styling such as spinners, progress bars and colors. Styling is automatically disabled if not attacched to a tty (default false)
|
--log-format string Set formatting for logs (color, text, json) (default "color")
|
||||||
-v, --verbose Enable verbose logs (default false)
|
--log-level string Set level for logs (info, warn, debug, trace) (default "info")
|
||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
|
@ -169,7 +171,7 @@ var artifactInstallTests = Describe("install", func() {
|
||||||
args = []string{artifactCmd, installCmd, "--config", configFile}
|
args = []string{artifactCmd, installCmd, "--config", configFile}
|
||||||
})
|
})
|
||||||
installAssertFailedBehavior(artifactInstallUsage,
|
installAssertFailedBehavior(artifactInstallUsage,
|
||||||
"ERRO: no artifacts to install, please configure artifacts or pass them as arguments to this command")
|
"ERROR no artifacts to install, please configure artifacts or pass them as arguments to this command")
|
||||||
})
|
})
|
||||||
|
|
||||||
When("unreachable registry", func() {
|
When("unreachable registry", func() {
|
||||||
|
@ -180,7 +182,7 @@ var artifactInstallTests = Describe("install", func() {
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
args = []string{artifactCmd, installCmd, "noregistry/testrules", "--plain-http", "--config", configFile}
|
args = []string{artifactCmd, installCmd, "noregistry/testrules", "--plain-http", "--config", configFile}
|
||||||
})
|
})
|
||||||
installAssertFailedBehavior(artifactInstallUsage, `ERRO: unable to fetch reference`)
|
installAssertFailedBehavior(artifactInstallUsage, `ERROR unable to get manifest: unable to fetch reference`)
|
||||||
})
|
})
|
||||||
|
|
||||||
When("invalid repository", func() {
|
When("invalid repository", func() {
|
||||||
|
@ -192,7 +194,7 @@ var artifactInstallTests = Describe("install", func() {
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
args = []string{artifactCmd, installCmd, newReg, "--plain-http", "--config", configFile}
|
args = []string{artifactCmd, installCmd, newReg, "--plain-http", "--config", configFile}
|
||||||
})
|
})
|
||||||
installAssertFailedBehavior(artifactInstallUsage, fmt.Sprintf("ERRO: unable to fetch reference %q", newReg))
|
installAssertFailedBehavior(artifactInstallUsage, fmt.Sprintf("ERROR unable to get manifest: unable to fetch reference %q", newReg))
|
||||||
})
|
})
|
||||||
|
|
||||||
When("with disallowed types (rulesfile)", func() {
|
When("with disallowed types (rulesfile)", func() {
|
||||||
|
@ -217,11 +219,11 @@ var artifactInstallTests = Describe("install", func() {
|
||||||
Expect(result).ToNot(BeNil())
|
Expect(result).ToNot(BeNil())
|
||||||
ref = registry + repoAndTag
|
ref = registry + repoAndTag
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
args = []string{artifactCmd, installCmd, ref, "--plain-http",
|
args = []string{artifactCmd, installCmd, ref, "--plain-http", "--platform", testPluginPlatform1,
|
||||||
"--config", configFilePath, "--allowed-types", "rulesfile"}
|
"--config", configFilePath, "--allowed-types", "rulesfile"}
|
||||||
})
|
})
|
||||||
|
|
||||||
installAssertFailedBehavior(artifactInstallUsage, "ERRO: cannot download artifact of type \"plugin\": type not permitted")
|
installAssertFailedBehavior(artifactInstallUsage, "ERROR cannot download artifact of type \"plugin\": type not permitted")
|
||||||
})
|
})
|
||||||
|
|
||||||
When("with disallowed types (plugin)", func() {
|
When("with disallowed types (plugin)", func() {
|
||||||
|
@ -250,7 +252,7 @@ var artifactInstallTests = Describe("install", func() {
|
||||||
"--config", configFilePath, "--allowed-types", "plugin"}
|
"--config", configFilePath, "--allowed-types", "plugin"}
|
||||||
})
|
})
|
||||||
|
|
||||||
installAssertFailedBehavior(artifactInstallUsage, "ERRO: cannot download artifact of type \"rulesfile\": type not permitted")
|
installAssertFailedBehavior(artifactInstallUsage, "ERROR cannot download artifact of type \"rulesfile\": type not permitted")
|
||||||
})
|
})
|
||||||
|
|
||||||
When("an unknown type is used", func() {
|
When("an unknown type is used", func() {
|
||||||
|
@ -280,7 +282,7 @@ var artifactInstallTests = Describe("install", func() {
|
||||||
"--config", configFilePath, "--allowed-types", "plugin," + wrongType}
|
"--config", configFilePath, "--allowed-types", "plugin," + wrongType}
|
||||||
})
|
})
|
||||||
|
|
||||||
installAssertFailedBehavior(artifactInstallUsage, fmt.Sprintf("ERRO: invalid argument \"plugin,%s\" for \"--allowed-types\" flag: "+
|
installAssertFailedBehavior(artifactInstallUsage, fmt.Sprintf("ERROR invalid argument \"plugin,%s\" for \"--allowed-types\" flag: "+
|
||||||
"not valid token %q: must be one of \"rulesfile\", \"plugin\"", wrongType, wrongType))
|
"not valid token %q: must be one of \"rulesfile\", \"plugin\"", wrongType, wrongType))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -309,12 +311,12 @@ var artifactInstallTests = Describe("install", func() {
|
||||||
Expect(result).ToNot(BeNil())
|
Expect(result).ToNot(BeNil())
|
||||||
ref = registry + repoAndTag
|
ref = registry + repoAndTag
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
args = []string{artifactCmd, installCmd, ref, "--plain-http",
|
args = []string{artifactCmd, installCmd, ref, "--plain-http", "--platform", testPluginPlatform1,
|
||||||
"--config", configFilePath, "--plugins-dir", destDir}
|
"--config", configFilePath, "--plugins-dir", destDir}
|
||||||
})
|
})
|
||||||
|
|
||||||
It("check that fails and the usage is not printed", func() {
|
It("check that fails and the usage is not printed", func() {
|
||||||
expectedError := fmt.Sprintf("ERRO: cannot use directory %q "+
|
expectedError := fmt.Sprintf("ERROR cannot use directory %q "+
|
||||||
"as install destination: %s is not writable", destDir, destDir)
|
"as install destination: %s is not writable", destDir, destDir)
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(err).To(HaveOccurred())
|
||||||
Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(artifactInstallUsage)))
|
Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(artifactInstallUsage)))
|
||||||
|
@ -347,12 +349,12 @@ var artifactInstallTests = Describe("install", func() {
|
||||||
Expect(result).ToNot(BeNil())
|
Expect(result).ToNot(BeNil())
|
||||||
ref = registry + repoAndTag
|
ref = registry + repoAndTag
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
args = []string{artifactCmd, installCmd, ref, "--plain-http",
|
args = []string{artifactCmd, installCmd, ref, "--plain-http", "--platform", testPluginPlatform1,
|
||||||
"--config", configFilePath, "--plugins-dir", destDir}
|
"--config", configFilePath, "--plugins-dir", destDir}
|
||||||
})
|
})
|
||||||
|
|
||||||
It("check that fails and the usage is not printed", func() {
|
It("check that fails and the usage is not printed", func() {
|
||||||
expectedError := fmt.Sprintf("ERRO: cannot use directory %q "+
|
expectedError := fmt.Sprintf("ERROR cannot use directory %q "+
|
||||||
"as install destination: %s doesn't exists", destDir, destDir)
|
"as install destination: %s doesn't exists", destDir, destDir)
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(err).To(HaveOccurred())
|
||||||
Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(artifactInstallUsage)))
|
Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(artifactInstallUsage)))
|
||||||
|
@ -390,7 +392,7 @@ var artifactInstallTests = Describe("install", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("check that fails and the usage is not printed", func() {
|
It("check that fails and the usage is not printed", func() {
|
||||||
expectedError := fmt.Sprintf("ERRO: cannot use directory %q "+
|
expectedError := fmt.Sprintf("ERROR cannot use directory %q "+
|
||||||
"as install destination: %s is not writable", destDir, destDir)
|
"as install destination: %s is not writable", destDir, destDir)
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(err).To(HaveOccurred())
|
||||||
Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(artifactInstallUsage)))
|
Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(artifactInstallUsage)))
|
||||||
|
@ -428,7 +430,7 @@ var artifactInstallTests = Describe("install", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("check that fails and the usage is not printed", func() {
|
It("check that fails and the usage is not printed", func() {
|
||||||
expectedError := fmt.Sprintf("ERRO: cannot use directory %q "+
|
expectedError := fmt.Sprintf("ERROR cannot use directory %q "+
|
||||||
"as install destination: %s doesn't exists", destDir, destDir)
|
"as install destination: %s doesn't exists", destDir, destDir)
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(err).To(HaveOccurred())
|
||||||
Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(artifactInstallUsage)))
|
Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(artifactInstallUsage)))
|
||||||
|
@ -436,6 +438,28 @@ var artifactInstallTests = Describe("install", func() {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
When("not --platform is not of the correct format", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
destDir = GinkgoT().TempDir()
|
||||||
|
err = os.Remove(destDir)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
baseDir := GinkgoT().TempDir()
|
||||||
|
configFilePath := baseDir + "/config.yaml"
|
||||||
|
content := []byte(correctIndexConfig)
|
||||||
|
err := os.WriteFile(configFilePath, content, 0o644)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
|
ref = registry + repoAndTag
|
||||||
|
args = []string{artifactCmd, installCmd, ref, "--config", configFile, "--platform", "this/is/invalid"}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("check that fails and the usage is not printed", func() {
|
||||||
|
expectedError := `ERROR invalid "platform": must be in the format OS/Arch`
|
||||||
|
Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(artifactInstallUsage)))
|
||||||
|
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(expectedError)))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2022 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -46,14 +47,12 @@ func NewArtifactListCmd(ctx context.Context, opt *options.Common) *cobra.Command
|
||||||
Short: "List all artifacts",
|
Short: "List all artifacts",
|
||||||
Long: "List all artifacts",
|
Long: "List all artifacts",
|
||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
SilenceErrors: true,
|
|
||||||
SilenceUsage: true,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return o.RunArtifactList(ctx, args)
|
return o.RunArtifactList(ctx, args)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().Var(&o.artifactType, "type", `Only list artifacts with a specific type. Allowed values: "rulesfile", "plugin""`)
|
cmd.Flags().Var(&o.artifactType, "type", `Only list artifacts with a specific type. Allowed values: "rulesfile", "plugin", "asset"`)
|
||||||
cmd.Flags().StringVar(&o.index, "index", "", "Only display artifacts from a configured index")
|
cmd.Flags().StringVar(&o.index, "index", "", "Only display artifacts from a configured index")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2022 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package manifest defines the business logic to fetch manifest layer for artifacts.
|
||||||
|
package manifest
|
|
@ -0,0 +1,93 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package manifest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
ocipuller "github.com/falcosecurity/falcoctl/pkg/oci/puller"
|
||||||
|
ociutils "github.com/falcosecurity/falcoctl/pkg/oci/utils"
|
||||||
|
"github.com/falcosecurity/falcoctl/pkg/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
type artifactManifestOptions struct {
|
||||||
|
*options.Common
|
||||||
|
*options.Registry
|
||||||
|
platform string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewArtifactManifestCmd returns the artifact manifest command.
|
||||||
|
func NewArtifactManifestCmd(ctx context.Context, opt *options.Common) *cobra.Command {
|
||||||
|
o := artifactManifestOptions{
|
||||||
|
Common: opt,
|
||||||
|
Registry: &options.Registry{},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "manifest [ref] [flags]",
|
||||||
|
Short: "Get the manifest layer of an artifact",
|
||||||
|
Long: "Get the manifest layer of an artifact",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return o.RunArtifactManifest(ctx, args)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
o.Registry.AddFlags(cmd)
|
||||||
|
cmd.Flags().StringVar(&o.platform, "platform", fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
|
||||||
|
"os and architecture of the artifact in OS/ARCH format")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *artifactManifestOptions) RunArtifactManifest(ctx context.Context, args []string) error {
|
||||||
|
var (
|
||||||
|
puller *ocipuller.Puller
|
||||||
|
ref string
|
||||||
|
manifest []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create puller with auto login enabled.
|
||||||
|
if puller, err = ociutils.Puller(o.PlainHTTP, o.Printer); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve the artifact reference.
|
||||||
|
if ref, err = o.IndexCache.ResolveReference(args[0]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: implement two new flags (platforms, platform) based on the oci platform struct.
|
||||||
|
// Split the platform.
|
||||||
|
tokens := strings.Split(o.platform, "/")
|
||||||
|
if len(tokens) != 2 {
|
||||||
|
return fmt.Errorf("invalid platform format: %s", o.platform)
|
||||||
|
}
|
||||||
|
|
||||||
|
if manifest, err = puller.RawManifest(ctx, ref, tokens[0], tokens[1]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
o.Printer.DefaultText.Println(string(manifest))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package manifest_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/distribution/distribution/v3/configuration"
|
||||||
|
_ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/onsi/gomega/gbytes"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"oras.land/oras-go/v2/registry/remote"
|
||||||
|
"oras.land/oras-go/v2/registry/remote/auth"
|
||||||
|
|
||||||
|
"github.com/falcosecurity/falcoctl/cmd"
|
||||||
|
"github.com/falcosecurity/falcoctl/pkg/oci"
|
||||||
|
"github.com/falcosecurity/falcoctl/pkg/oci/authn"
|
||||||
|
ocipusher "github.com/falcosecurity/falcoctl/pkg/oci/pusher"
|
||||||
|
commonoptions "github.com/falcosecurity/falcoctl/pkg/options"
|
||||||
|
testutils "github.com/falcosecurity/falcoctl/pkg/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
localRegistryHost string
|
||||||
|
localRegistry *remote.Registry
|
||||||
|
testRuleTarball = "../../../pkg/test/data/rules.tar.gz"
|
||||||
|
testPluginTarball = "../../../pkg/test/data/plugin.tar.gz"
|
||||||
|
testPluginPlatform1 = "linux/amd64"
|
||||||
|
testPluginPlatform2 = "windows/amd64"
|
||||||
|
testPluginPlatform3 = "linux/arm64"
|
||||||
|
ctx = context.Background()
|
||||||
|
pluginMultiPlatformRef string
|
||||||
|
rulesRef string
|
||||||
|
output = gbytes.NewBuffer()
|
||||||
|
rootCmd *cobra.Command
|
||||||
|
opt *commonoptions.Common
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestManifest(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Manifest Suite")
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = BeforeSuite(func() {
|
||||||
|
var err error
|
||||||
|
config := &configuration.Configuration{}
|
||||||
|
// Get a free port to be used by the registry.
|
||||||
|
port, err := testutils.FreePort()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
// Create the registry address to which will bind.
|
||||||
|
config.HTTP.Addr = fmt.Sprintf("localhost:%d", port)
|
||||||
|
localRegistryHost = config.HTTP.Addr
|
||||||
|
|
||||||
|
// Create the oras registry.
|
||||||
|
localRegistry, err = testutils.NewOrasRegistry(localRegistryHost, true)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
// Start the local registry.
|
||||||
|
go func() {
|
||||||
|
err := testutils.StartRegistry(context.Background(), config)
|
||||||
|
Expect(err).ToNot(BeNil())
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Check that the registry is up and accepting connections.
|
||||||
|
Eventually(func(g Gomega) error {
|
||||||
|
res, err := http.Get(fmt.Sprintf("http://%s", config.HTTP.Addr))
|
||||||
|
g.Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
g.Expect(res.StatusCode).Should(Equal(http.StatusOK))
|
||||||
|
return err
|
||||||
|
}).WithTimeout(time.Second * 5).ShouldNot(HaveOccurred())
|
||||||
|
|
||||||
|
// Initialize options for command.
|
||||||
|
opt = commonoptions.NewOptions()
|
||||||
|
opt.Initialize(commonoptions.WithWriter(output))
|
||||||
|
|
||||||
|
// Push the artifacts to the registry.
|
||||||
|
// Same artifacts will be used to test the puller code.
|
||||||
|
pusher := ocipusher.NewPusher(authn.NewClient(authn.WithCredentials(&auth.EmptyCredential)), true, nil)
|
||||||
|
|
||||||
|
// Push plugin artifact with multiple architectures.
|
||||||
|
filePathsAndPlatforms := ocipusher.WithFilepathsAndPlatforms([]string{testPluginTarball, testPluginTarball, testPluginTarball},
|
||||||
|
[]string{testPluginPlatform1, testPluginPlatform2, testPluginPlatform3})
|
||||||
|
pluginMultiPlatformRef = localRegistryHost + "/plugins:multiplatform"
|
||||||
|
artConfig := oci.ArtifactConfig{}
|
||||||
|
Expect(artConfig.ParseDependencies("my-dep:1.2.3|my-alt-dep:1.4.5")).ToNot(HaveOccurred())
|
||||||
|
Expect(artConfig.ParseRequirements("my-req:7.8.9")).ToNot(HaveOccurred())
|
||||||
|
artifactConfig := ocipusher.WithArtifactConfig(artConfig)
|
||||||
|
|
||||||
|
// Build options slice.
|
||||||
|
options := []ocipusher.Option{filePathsAndPlatforms, artifactConfig}
|
||||||
|
|
||||||
|
// Push the plugin artifact.
|
||||||
|
_, err = pusher.Push(ctx, oci.Plugin, pluginMultiPlatformRef, options...)
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
|
||||||
|
// Prepare and push artifact without config layer.
|
||||||
|
filePaths := ocipusher.WithFilepaths([]string{testRuleTarball})
|
||||||
|
artConfig = oci.ArtifactConfig{}
|
||||||
|
Expect(artConfig.ParseDependencies("dep1:1.2.3", "dep2:2.3.1")).ToNot(HaveOccurred())
|
||||||
|
options = []ocipusher.Option{
|
||||||
|
filePaths,
|
||||||
|
ocipusher.WithTags("latest"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push a rulesfile artifact
|
||||||
|
options = append(options, ocipusher.WithArtifactConfig(artConfig))
|
||||||
|
rulesRef = localRegistryHost + "/rulesfiles:regular"
|
||||||
|
_, err = pusher.Push(ctx, oci.Rulesfile, rulesRef, options...)
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
func executeRoot(args []string) error {
|
||||||
|
rootCmd.SetArgs(args)
|
||||||
|
rootCmd.SetOut(output)
|
||||||
|
return cmd.Execute(rootCmd, opt)
|
||||||
|
}
|
|
@ -0,0 +1,204 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
package manifest_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/onsi/gomega/gbytes"
|
||||||
|
|
||||||
|
"github.com/falcosecurity/falcoctl/cmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
var usage = `Usage:
|
||||||
|
falcoctl artifact manifest [ref] [flags]
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-h, --help help for manifest
|
||||||
|
--plain-http allows interacting with remote registry via plain http requests
|
||||||
|
--platform string os and architecture of the artifact in OS/ARCH format (default "linux/amd64")
|
||||||
|
|
||||||
|
Global Flags:
|
||||||
|
--config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml")
|
||||||
|
--log-format string Set formatting for logs (color, text, json) (default "color")
|
||||||
|
--log-level string Set level for logs (info, warn, debug, trace) (default "info")
|
||||||
|
`
|
||||||
|
|
||||||
|
var help = `Get the manifest layer of an artifact
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
falcoctl artifact manifest [ref] [flags]
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-h, --help help for manifest
|
||||||
|
--plain-http allows interacting with remote registry via plain http requests
|
||||||
|
--platform string os and architecture of the artifact in OS/ARCH format (default "linux/amd64")
|
||||||
|
|
||||||
|
Global Flags:
|
||||||
|
--config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml")
|
||||||
|
--log-format string Set formatting for logs (color, text, json) (default "color")
|
||||||
|
--log-level string Set level for logs (info, warn, debug, trace) (default "info")
|
||||||
|
`
|
||||||
|
|
||||||
|
var _ = Describe("Manifest", func() {
|
||||||
|
const (
|
||||||
|
artifactCmd = "artifact"
|
||||||
|
manifestCmd = "manifest"
|
||||||
|
plaingHTTP = "--plain-http"
|
||||||
|
configFlag = "--config"
|
||||||
|
platformFlag = "--platform"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
args []string
|
||||||
|
configDir string
|
||||||
|
)
|
||||||
|
|
||||||
|
var assertFailedBehavior = func(usage, specificError string) {
|
||||||
|
It("check that fails and the usage is not printed", func() {
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(usage)))
|
||||||
|
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(specificError)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
JustBeforeEach(func() {
|
||||||
|
configDir = GinkgoT().TempDir()
|
||||||
|
rootCmd = cmd.New(ctx, opt)
|
||||||
|
err = executeRoot(args)
|
||||||
|
})
|
||||||
|
|
||||||
|
JustAfterEach(func() {
|
||||||
|
err = nil
|
||||||
|
Expect(output.Clear()).ShouldNot(HaveOccurred())
|
||||||
|
args = nil
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("help message", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{artifactCmd, manifestCmd, "--help"}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should match the saved one", func() {
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
Expect(string(output.Contents())).Should(Equal(help))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("wrong number of arguments", func() {
|
||||||
|
When("number of arguments equal to 0", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{artifactCmd, manifestCmd}
|
||||||
|
})
|
||||||
|
|
||||||
|
assertFailedBehavior(usage, "ERROR accepts 1 arg(s), received 0 ")
|
||||||
|
})
|
||||||
|
|
||||||
|
When("number of arguments equal to 2", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{artifactCmd, manifestCmd, "arg1", "arg2", configFlag, configDir}
|
||||||
|
})
|
||||||
|
|
||||||
|
assertFailedBehavior(usage, "ERROR accepts 1 arg(s), received 2 ")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("failure", func() {
|
||||||
|
When("unreachable/non existing registry", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{artifactCmd, manifestCmd, "noregistry/noartifact", plaingHTTP, configFlag, configDir}
|
||||||
|
})
|
||||||
|
|
||||||
|
assertFailedBehavior(usage, "ERROR unable to fetch reference \"noregistry/noartifact:latest\"")
|
||||||
|
})
|
||||||
|
|
||||||
|
When("non existing repository", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{artifactCmd, manifestCmd, localRegistryHost + "/noartifact", plaingHTTP, configFlag, configDir}
|
||||||
|
})
|
||||||
|
|
||||||
|
assertFailedBehavior(usage, "noartifact:latest: not found")
|
||||||
|
})
|
||||||
|
|
||||||
|
When("non parsable reference", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{artifactCmd, manifestCmd, " ", plaingHTTP, configFlag, configDir}
|
||||||
|
})
|
||||||
|
|
||||||
|
assertFailedBehavior(usage, "ERROR cannot find among the configured indexes, skipping ")
|
||||||
|
})
|
||||||
|
|
||||||
|
When("no manifest for given platform", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{artifactCmd, manifestCmd, pluginMultiPlatformRef, plaingHTTP, configFlag, configDir, platformFlag, "linux/wrong"}
|
||||||
|
})
|
||||||
|
assertFailedBehavior(usage, "ERROR unable to find a manifest matching the given platform: linux/wrong")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("success", func() {
|
||||||
|
When("without image index and no platform (rulesfiles)", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{artifactCmd, manifestCmd, rulesRef, plaingHTTP, configFlag, configDir}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should success", func() {
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(`{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.cncf.falco.rulesfile.config.v1+json","digest":"sha256:c329db306d80e7f1e3a5df28bb7d75a0a1545ad1e8f717a4ab4534a3d558affa","size":86},"layers":[{"mediaType":"application/vnd.cncf.falco.rulesfile.layer.v1+tar.gz","digest":"sha256:8ed676f9801d987a26854827beb176eb9164dec3b09a714406348fe1096f7c6c","size":2560,"annotations":{"org.opencontainers.image.title":"rules.tar.gz"}}],"annotations":{"org.opencontainers.image.created":`))) //nolint:lll //testing purpose
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
When("no platform flag", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{artifactCmd, manifestCmd, pluginMultiPlatformRef, plaingHTTP, configFlag, configDir}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should success getting the platform where tests are running", func() {
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(
|
||||||
|
`{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.cncf.falco.plugin.config.v1+json","digest":"sha256:39ae8c14fd9ef38d0f1836ba7be71627023ce615f165c3663586a325eee04724","size":164},"layers":[{"mediaType":"application/vnd.cncf.falco.plugin.layer.v1+tar.gz","digest":"sha256:45a192b10e9bbfc82f4216b071afefd7fba56e02e856e37186430d40160e5d64","size":6659921,"annotations":{"org.opencontainers.image.title":"plugin.tar.gz"}}],"annotations":{"org.opencontainers.image.created":`))) //nolint:lll //testing purpose
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
When("with valid platform", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{artifactCmd, manifestCmd, pluginMultiPlatformRef, plaingHTTP, configFlag, configDir, platformFlag, testPluginPlatform3}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should success", func() {
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(
|
||||||
|
`{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.cncf.falco.plugin.config.v1+json","digest":"sha256:39ae8c14fd9ef38d0f1836ba7be71627023ce615f165c3663586a325eee04724","size":164},"layers":[{"mediaType":"application/vnd.cncf.falco.plugin.layer.v1+tar.gz","digest":"sha256:45a192b10e9bbfc82f4216b071afefd7fba56e02e856e37186430d40160e5d64","size":6659921,"annotations":{"org.opencontainers.image.title":"plugin.tar.gz"}}],"annotations":{"org.opencontainers.image.created":`))) //nolint:lll //testing purpose
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
When("with non existing platform for artifacts without platforms", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{artifactCmd, manifestCmd, rulesRef, plaingHTTP, configFlag, configDir, platformFlag, testPluginPlatform3}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should success and ignore the platform flag", func() {
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(
|
||||||
|
`{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.cncf.falco.rulesfile.config.v1+json","digest":"sha256:c329db306d80e7f1e3a5df28bb7d75a0a1545ad1e8f717a4ab4534a3d558affa","size":86},"layers":[{"mediaType":"application/vnd.cncf.falco.rulesfile.layer.v1+tar.gz","digest":"sha256:8ed676f9801d987a26854827beb176eb9164dec3b09a714406348fe1096f7c6c","size":2560,"annotations":{"org.opencontainers.image.title":"rules.tar.gz"}}],"annotations":{"org.opencontainers.image.created":`))) //nolint:lll //testing purpose
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2022 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -57,8 +58,6 @@ func NewArtifactSearchCmd(ctx context.Context, opt *options.Common) *cobra.Comma
|
||||||
Short: "Search an artifact by keywords",
|
Short: "Search an artifact by keywords",
|
||||||
Long: "Search an artifact by keywords",
|
Long: "Search an artifact by keywords",
|
||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.MinimumNArgs(1),
|
||||||
SilenceErrors: true,
|
|
||||||
SilenceUsage: true,
|
|
||||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return o.Validate()
|
return o.Validate()
|
||||||
},
|
},
|
||||||
|
@ -70,7 +69,7 @@ func NewArtifactSearchCmd(ctx context.Context, opt *options.Common) *cobra.Comma
|
||||||
cmd.Flags().Float64VarP(&o.minScore, "min-score", "", defaultMinScore,
|
cmd.Flags().Float64VarP(&o.minScore, "min-score", "", defaultMinScore,
|
||||||
"the minimum score used to match artifact names with search keywords")
|
"the minimum score used to match artifact names with search keywords")
|
||||||
|
|
||||||
cmd.Flags().Var(&o.artifactType, "type", `Only search artifacts with a specific type. Allowed values: "rulesfile", "plugin""`)
|
cmd.Flags().Var(&o.artifactType, "type", `Only search artifacts with a specific type. Allowed values: "rulesfile", "plugin", "asset"`)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2022 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
132
cmd/cli_test.go
132
cmd/cli_test.go
|
@ -1,132 +0,0 @@
|
||||||
// Copyright 2022 The Falco Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/acarl005/stripansi"
|
|
||||||
"gotest.tools/assert"
|
|
||||||
|
|
||||||
"github.com/falcosecurity/falcoctl/pkg/options"
|
|
||||||
)
|
|
||||||
|
|
||||||
type expect struct {
|
|
||||||
err string
|
|
||||||
out string
|
|
||||||
}
|
|
||||||
|
|
||||||
type testCase struct {
|
|
||||||
descr string
|
|
||||||
env map[string]string
|
|
||||||
args []string
|
|
||||||
expect expect
|
|
||||||
}
|
|
||||||
|
|
||||||
var tests = []testCase{
|
|
||||||
{
|
|
||||||
descr: "no-args-no-flags",
|
|
||||||
args: []string{},
|
|
||||||
expect: expect{
|
|
||||||
out: "testdata/noargsnoflags.txt",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
descr: "wrong-flag",
|
|
||||||
args: []string{"--wrong"},
|
|
||||||
expect: expect{
|
|
||||||
out: "testdata/wrongflag.txt",
|
|
||||||
err: "unknown flag: --wrong",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
args: []string{"help"},
|
|
||||||
expect: expect{
|
|
||||||
out: "testdata/help.txt",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
descr: "help-flag",
|
|
||||||
args: []string{"--help"},
|
|
||||||
expect: expect{
|
|
||||||
out: "testdata/help.txt",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func run(t *testing.T, test *testCase) {
|
|
||||||
// Setup
|
|
||||||
c := New(context.Background(), &options.Common{})
|
|
||||||
o := bytes.NewBufferString("")
|
|
||||||
c.SetOut(o)
|
|
||||||
c.SetErr(o)
|
|
||||||
c.SetArgs(test.args)
|
|
||||||
for k, v := range test.env {
|
|
||||||
if err := os.Setenv(k, v); err != nil {
|
|
||||||
t.Fatalf("error setting env variables: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Test
|
|
||||||
err := c.Execute()
|
|
||||||
if err != nil {
|
|
||||||
if test.expect.err == "" {
|
|
||||||
t.Fatalf("error executing CLI: %v", err)
|
|
||||||
} else {
|
|
||||||
assert.Error(t, err, test.expect.err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := io.ReadAll(o)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error reading CLI output: %v", err)
|
|
||||||
}
|
|
||||||
res := stripansi.Strip(string(out))
|
|
||||||
assert.Equal(t, test.expect.out, res)
|
|
||||||
// Teardown
|
|
||||||
for k := range test.env {
|
|
||||||
if err := os.Unsetenv(k); err != nil {
|
|
||||||
t.Fatalf("error tearing down: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCLI(t *testing.T) {
|
|
||||||
for _, test := range tests {
|
|
||||||
descr := test.descr
|
|
||||||
if descr == "" {
|
|
||||||
if test.expect.out == "" {
|
|
||||||
t.Fatal("malformed test case: missing both descr and expect.out fields")
|
|
||||||
}
|
|
||||||
test.descr = strings.TrimSuffix(filepath.Base(test.expect.out), ".txt")
|
|
||||||
}
|
|
||||||
if test.expect.out != "" {
|
|
||||||
out, err := os.ReadFile(test.expect.out)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("output fixture not found: %v", err)
|
|
||||||
}
|
|
||||||
test.expect.out = string(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run(test.descr, func(t *testing.T) {
|
|
||||||
run(t, &test)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package cmd_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCmd(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Cmd Suite")
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2022 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package drivercleanup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pterm/pterm"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
|
"github.com/falcosecurity/falcoctl/pkg/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
type driverCleanupOptions struct {
|
||||||
|
*options.Common
|
||||||
|
*options.Driver
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDriverCleanupCmd cleans a driver up.
|
||||||
|
func NewDriverCleanupCmd(ctx context.Context, opt *options.Common, driver *options.Driver) *cobra.Command {
|
||||||
|
o := driverCleanupOptions{
|
||||||
|
Common: opt,
|
||||||
|
Driver: driver,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "cleanup [flags]",
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
|
Short: "Cleanup a driver",
|
||||||
|
Long: `Cleans a driver up, eg for kmod, by removing it from dkms.`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return o.RunDriverCleanup(ctx)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *driverCleanupOptions) RunDriverCleanup(_ context.Context) error {
|
||||||
|
o.Printer.Logger.Info("Running falcoctl driver cleanup", o.Printer.Logger.Args(
|
||||||
|
"driver type", o.Driver.Type,
|
||||||
|
"driver name", o.Driver.Name))
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if !o.Printer.DisableStyling {
|
||||||
|
o.Printer.Spinner, _ = o.Printer.Spinner.Start("Cleaning up existing drivers")
|
||||||
|
}
|
||||||
|
err := o.Driver.Type.Cleanup(o.Printer.WithWriter(&buf), o.Driver.Name)
|
||||||
|
if o.Printer.Spinner != nil {
|
||||||
|
_ = o.Printer.Spinner.Stop()
|
||||||
|
}
|
||||||
|
if o.Printer.Logger.Formatter == pterm.LogFormatterJSON {
|
||||||
|
// Only print formatted text if we are formatting to json
|
||||||
|
out := strings.ReplaceAll(buf.String(), "\n", ";")
|
||||||
|
o.Printer.Logger.Info("Driver cleanup", o.Printer.Logger.Args("output", out))
|
||||||
|
} else {
|
||||||
|
// Print much more readable output as-is
|
||||||
|
o.Printer.DefaultText.Print(buf.String())
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package drivercleanup_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/onsi/gomega/gbytes"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/falcosecurity/falcoctl/cmd"
|
||||||
|
commonoptions "github.com/falcosecurity/falcoctl/pkg/options"
|
||||||
|
testutils "github.com/falcosecurity/falcoctl/pkg/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ctx = context.Background()
|
||||||
|
output = gbytes.NewBuffer()
|
||||||
|
rootCmd *cobra.Command
|
||||||
|
opt *commonoptions.Common
|
||||||
|
configFile string
|
||||||
|
err error
|
||||||
|
args []string
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCleanup(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Cleanup Suite")
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = BeforeSuite(func() {
|
||||||
|
|
||||||
|
// Create and configure the common options.
|
||||||
|
opt = commonoptions.NewOptions()
|
||||||
|
opt.Initialize(commonoptions.WithWriter(output))
|
||||||
|
|
||||||
|
// Create temporary directory used to save the configuration file.
|
||||||
|
configFile, err = testutils.CreateEmptyFile("falcoctl.yaml")
|
||||||
|
Expect(err).Should(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
var _ = AfterSuite(func() {
|
||||||
|
configDir := filepath.Dir(configFile)
|
||||||
|
Expect(os.RemoveAll(configDir)).Should(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
func executeRoot(args []string) error {
|
||||||
|
rootCmd.SetArgs(args)
|
||||||
|
rootCmd.SetOut(output)
|
||||||
|
return cmd.Execute(rootCmd, opt)
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2024 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package drivercleanup_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/onsi/gomega/gbytes"
|
||||||
|
|
||||||
|
"github.com/falcosecurity/falcoctl/cmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
//nolint:lll // no need to check for line length.
|
||||||
|
var driverCleanupHelp = `Cleans a driver up, eg for kmod, by removing it from dkms.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
falcoctl driver cleanup [flags]
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-h, --help help for cleanup
|
||||||
|
|
||||||
|
Global Flags:
|
||||||
|
--config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml")
|
||||||
|
--host-root string Driver host root to be used. (default "/")
|
||||||
|
--kernelrelease string Specify the kernel release for which to download/build the driver in the same format used by 'uname -r' (e.g. '6.1.0-10-cloud-amd64')
|
||||||
|
--kernelversion string Specify the kernel version for which to download/build the driver in the same format used by 'uname -v' (e.g. '#1 SMP PREEMPT_DYNAMIC Debian 6.1.38-2 (2023-07-27)')
|
||||||
|
--log-format string Set formatting for logs (color, text, json) (default "color")
|
||||||
|
--log-level string Set level for logs (info, warn, debug, trace) (default "info")
|
||||||
|
--name string Driver name to be used. (default "falco")
|
||||||
|
--repo strings Driver repo to be used. (default [https://download.falco.org/driver])
|
||||||
|
--type strings Driver types allowed in descending priority order (ebpf, kmod, modern_ebpf) (default [modern_ebpf,kmod,ebpf])
|
||||||
|
--version string Driver version to be used.
|
||||||
|
`
|
||||||
|
|
||||||
|
var addAssertFailedBehavior = func(specificError string) {
|
||||||
|
It("check that fails and the usage is not printed", func() {
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(specificError)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Describe("cleanup", func() {
|
||||||
|
|
||||||
|
var (
|
||||||
|
driverCmd = "driver"
|
||||||
|
cleanupCmd = "cleanup"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Each test gets its own root command and runs it.
|
||||||
|
// The err variable is asserted by each test.
|
||||||
|
JustBeforeEach(func() {
|
||||||
|
rootCmd = cmd.New(ctx, opt)
|
||||||
|
err = executeRoot(args)
|
||||||
|
})
|
||||||
|
|
||||||
|
JustAfterEach(func() {
|
||||||
|
Expect(output.Clear()).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("help message", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{driverCmd, cleanupCmd, "--help"}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should match the saved one", func() {
|
||||||
|
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(driverCleanupHelp)))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Here we are testing failure cases for cleaning a driver.
|
||||||
|
Context("failure", func() {
|
||||||
|
When("with non absolute host-root", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{driverCmd, cleanupCmd, "--config", configFile, "--host-root", "foo/"}
|
||||||
|
})
|
||||||
|
addAssertFailedBehavior("ERROR host-root must be an absolute path (foo/)")
|
||||||
|
})
|
||||||
|
|
||||||
|
When("with invalid driver type", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{driverCmd, cleanupCmd, "--config", configFile, "--type", "foo"}
|
||||||
|
})
|
||||||
|
addAssertFailedBehavior(`ERROR unsupported driver type specified: foo`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,17 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package drivercleanup defines the cleanup logic for the driver cmd.
|
||||||
|
package drivercleanup
|
|
@ -0,0 +1,326 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2024 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
package driverconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/falcosecurity/driverkit/pkg/kernelrelease"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
|
|
||||||
|
drivertype "github.com/falcosecurity/falcoctl/pkg/driver/type"
|
||||||
|
"github.com/falcosecurity/falcoctl/pkg/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
falcoName = "falco"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newOptions() *driverConfigOptions {
|
||||||
|
common := options.NewOptions()
|
||||||
|
common.Initialize()
|
||||||
|
|
||||||
|
// Parse the driver type.
|
||||||
|
dType, _ := drivertype.Parse("modern_ebpf")
|
||||||
|
return &driverConfigOptions{
|
||||||
|
Common: common,
|
||||||
|
Driver: &options.Driver{
|
||||||
|
Type: dType,
|
||||||
|
Name: falcoName,
|
||||||
|
Repos: []string{"https://download.falco.org/driver"},
|
||||||
|
Version: "6.0.0+driver",
|
||||||
|
HostRoot: "/",
|
||||||
|
Distro: nil,
|
||||||
|
Kr: kernelrelease.KernelRelease{},
|
||||||
|
},
|
||||||
|
update: false,
|
||||||
|
namespace: "",
|
||||||
|
kubeconfig: "",
|
||||||
|
configmap: "",
|
||||||
|
configDir: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFalcoConfigFile(cfg falcoCfg, configDir string) error {
|
||||||
|
engineKind, err := yaml.Marshal(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to marshal falco config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the engine configuration to a specialized config file.
|
||||||
|
if err := os.WriteFile(filepath.Join(configDir, "falco.yaml"), engineKind, 0o600); err != nil {
|
||||||
|
return fmt.Errorf("unable to write falco.yaml file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFalcoConfigMap(cfg falcoCfg, dataKey string) (*v1.ConfigMap, error) {
|
||||||
|
engineKind, err := yaml.Marshal(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to marshal falco config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cm := &v1.ConfigMap{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: falcoName,
|
||||||
|
Namespace: falcoName,
|
||||||
|
},
|
||||||
|
Data: map[string]string{
|
||||||
|
dataKey: string(engineKind),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return cm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDriverConfigOptions_Commit_Host(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args func(t *testing.T) *driverConfigOptions
|
||||||
|
expected func(t *testing.T, opt *driverConfigOptions, err error)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"no falco config file",
|
||||||
|
func(t *testing.T) *driverConfigOptions {
|
||||||
|
opt := newOptions()
|
||||||
|
opt.configDir = "no-file-at-all"
|
||||||
|
opt.update = true
|
||||||
|
return opt
|
||||||
|
},
|
||||||
|
func(t *testing.T, opt *driverConfigOptions, err error) {
|
||||||
|
require.Error(t, err, "should error since falco configuration file does not exist")
|
||||||
|
require.ErrorContains(t, err, "open no-file-at-all/falco.yaml: no such file or directory")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"update-falco-config",
|
||||||
|
func(t *testing.T) *driverConfigOptions {
|
||||||
|
opt := newOptions()
|
||||||
|
dir, err := os.MkdirTemp("", "falcoctl-driver-config-test")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Write falco configuration file.
|
||||||
|
cfg := falcoCfg{engineCfg{Kind: "modern_ebpf"}}
|
||||||
|
err = createFalcoConfigFile(cfg, dir)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
opt.configDir = dir
|
||||||
|
return opt
|
||||||
|
},
|
||||||
|
func(t *testing.T, opt *driverConfigOptions, err error) {
|
||||||
|
require.NoError(t, err, "should not error")
|
||||||
|
|
||||||
|
// Config file.
|
||||||
|
specCfgFile := filepath.Join(opt.configDir, "config.d", falcoDriverConfigFile)
|
||||||
|
|
||||||
|
// Check that config file has been created.
|
||||||
|
_, err = os.Stat(specCfgFile)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
content, err := os.ReadFile(specCfgFile)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cfg := falcoCfg{}
|
||||||
|
err = yaml.Unmarshal(content, &cfg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, opt.Type.String(), cfg.Engine.Kind)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"falco-not-in-driver-mode",
|
||||||
|
func(t *testing.T) *driverConfigOptions {
|
||||||
|
opt := newOptions()
|
||||||
|
dir, err := os.MkdirTemp("", "falcoctl-driver-config-test")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Write falco configuration file.
|
||||||
|
cfg := falcoCfg{engineCfg{Kind: "nodriver"}}
|
||||||
|
err = createFalcoConfigFile(cfg, dir)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
opt.configDir = dir
|
||||||
|
return opt
|
||||||
|
},
|
||||||
|
func(t *testing.T, opt *driverConfigOptions, err error) {
|
||||||
|
require.NoError(t, err, "should not error")
|
||||||
|
|
||||||
|
// Config file.
|
||||||
|
specCfgFile := filepath.Join(opt.configDir, "config.d", falcoDriverConfigFile)
|
||||||
|
|
||||||
|
// Check that config file has been created.
|
||||||
|
_, err = os.Stat(specCfgFile)
|
||||||
|
require.True(t, os.IsNotExist(err))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
opt := testCase.args(t)
|
||||||
|
err := opt.Commit(context.Background(), nil, opt.Type)
|
||||||
|
testCase.expected(t, opt, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDriverConfigOptions_Commit_K8S(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args func(t *testing.T) (*driverConfigOptions, *v1.ConfigMap)
|
||||||
|
expected func(t *testing.T, opt *driverConfigOptions, err error)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"no falco configmap, wrong namespace",
|
||||||
|
func(t *testing.T) (*driverConfigOptions, *v1.ConfigMap) {
|
||||||
|
opt := newOptions()
|
||||||
|
opt.namespace = "wrong-namespace"
|
||||||
|
opt.configmap = falcoName
|
||||||
|
|
||||||
|
cm, err := createFalcoConfigMap(falcoCfg{engineCfg{Kind: "modern_ebpf"}}, "falco.yaml")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return opt, cm
|
||||||
|
},
|
||||||
|
func(t *testing.T, opt *driverConfigOptions, err error) {
|
||||||
|
require.Error(t, err, "should error since falco configmap does not exist")
|
||||||
|
require.ErrorContains(t, err, "unable to get configmap falco in namespace wrong-namespace")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"no falco configmap, wrong name",
|
||||||
|
func(t *testing.T) (*driverConfigOptions, *v1.ConfigMap) {
|
||||||
|
opt := newOptions()
|
||||||
|
opt.namespace = falcoName
|
||||||
|
opt.configmap = "wrong-name"
|
||||||
|
|
||||||
|
cm, err := createFalcoConfigMap(falcoCfg{engineCfg{Kind: "modern_ebpf"}}, "falco.yaml")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return opt, cm
|
||||||
|
},
|
||||||
|
func(t *testing.T, opt *driverConfigOptions, err error) {
|
||||||
|
require.Error(t, err, "should error since falco configmap does not exist")
|
||||||
|
require.ErrorContains(t, err, "unable to get configmap wrong-name in namespace falco")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"no falco config, wrong data key",
|
||||||
|
func(t *testing.T) (*driverConfigOptions, *v1.ConfigMap) {
|
||||||
|
opt := newOptions()
|
||||||
|
opt.namespace = falcoName
|
||||||
|
opt.configmap = falcoName
|
||||||
|
|
||||||
|
cm, err := createFalcoConfigMap(falcoCfg{engineCfg{Kind: "modern_ebpf"}}, "wrong-data-key")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return opt, cm
|
||||||
|
},
|
||||||
|
func(t *testing.T, opt *driverConfigOptions, err error) {
|
||||||
|
require.Error(t, err, "should error since falco configmap does not exist")
|
||||||
|
require.ErrorContains(t, err, "configMap falco does not contain key \"falco.yaml\"")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"update-falco-config",
|
||||||
|
func(t *testing.T) (*driverConfigOptions, *v1.ConfigMap) {
|
||||||
|
opt := newOptions()
|
||||||
|
opt.namespace = falcoName
|
||||||
|
opt.configmap = falcoName
|
||||||
|
|
||||||
|
dir, err := os.MkdirTemp("", "falcoctl-driver-config-test")
|
||||||
|
require.NoError(t, err)
|
||||||
|
opt.configDir = dir
|
||||||
|
|
||||||
|
cm, err := createFalcoConfigMap(falcoCfg{engineCfg{Kind: "modern_ebpf"}}, "falco.yaml")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return opt, cm
|
||||||
|
},
|
||||||
|
|
||||||
|
func(t *testing.T, opt *driverConfigOptions, err error) {
|
||||||
|
require.NoError(t, err, "should not error")
|
||||||
|
|
||||||
|
// Config file.
|
||||||
|
specCfgFile := filepath.Join(opt.configDir, "config.d", falcoDriverConfigFile)
|
||||||
|
|
||||||
|
// Check that config file has been created.
|
||||||
|
_, err = os.Stat(specCfgFile)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
content, err := os.ReadFile(specCfgFile)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cfg := falcoCfg{}
|
||||||
|
err = yaml.Unmarshal(content, &cfg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, opt.Type.String(), cfg.Engine.Kind)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"falco-not-in-driver-mode",
|
||||||
|
func(t *testing.T) (*driverConfigOptions, *v1.ConfigMap) {
|
||||||
|
opt := newOptions()
|
||||||
|
opt.namespace = falcoName
|
||||||
|
opt.configmap = falcoName
|
||||||
|
|
||||||
|
dir, err := os.MkdirTemp("", "falcoctl-driver-config-test")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cm, err := createFalcoConfigMap(falcoCfg{engineCfg{Kind: "nodriver"}}, "falco.yaml")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
opt.configDir = dir
|
||||||
|
return opt, cm
|
||||||
|
},
|
||||||
|
func(t *testing.T, opt *driverConfigOptions, err error) {
|
||||||
|
require.NoError(t, err, "should not error")
|
||||||
|
|
||||||
|
// Config file.
|
||||||
|
specCfgFile := filepath.Join(opt.configDir, "config.d", falcoDriverConfigFile)
|
||||||
|
|
||||||
|
// Check that config file has been created.
|
||||||
|
_, err = os.Stat(specCfgFile)
|
||||||
|
require.True(t, os.IsNotExist(err))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
opt, cm := testCase.args(t)
|
||||||
|
// Create fake client.
|
||||||
|
fakeClient := fake.NewSimpleClientset(cm)
|
||||||
|
err := opt.Commit(context.Background(), fakeClient, opt.Type)
|
||||||
|
testCase.expected(t, opt, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,262 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2024 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package driverconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
|
|
||||||
|
"github.com/falcosecurity/falcoctl/internal/config"
|
||||||
|
drivertype "github.com/falcosecurity/falcoctl/pkg/driver/type"
|
||||||
|
"github.com/falcosecurity/falcoctl/pkg/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
longConfig = `Configure a driver for future usages with other driver subcommands.
|
||||||
|
It will also update local Falco configuration or k8s configmap depending on the environment where it is running, to let Falco use chosen driver.
|
||||||
|
Only supports deployments of Falco that use a driver engine, ie: one between kmod, ebpf and modern-ebpf.
|
||||||
|
If engine.kind key is set to a non-driver driven engine, Falco configuration won't be touched.
|
||||||
|
`
|
||||||
|
falcoConfigFile = "falco.yaml"
|
||||||
|
falcoDriverConfigFile = "engine-kind-falcoctl.yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
type driverConfigOptions struct {
|
||||||
|
*options.Common
|
||||||
|
*options.Driver
|
||||||
|
update bool
|
||||||
|
namespace string
|
||||||
|
kubeconfig string
|
||||||
|
configmap string
|
||||||
|
configDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
type engineCfg struct {
|
||||||
|
Kind string `yaml:"kind"`
|
||||||
|
}
|
||||||
|
type falcoCfg struct {
|
||||||
|
Engine engineCfg `yaml:"engine"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDriverConfigCmd configures a driver and stores it in config.
|
||||||
|
func NewDriverConfigCmd(ctx context.Context, opt *options.Common, driver *options.Driver) *cobra.Command {
|
||||||
|
o := driverConfigOptions{
|
||||||
|
Common: opt,
|
||||||
|
Driver: driver,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "config [flags]",
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
|
Short: "Configure a driver",
|
||||||
|
Long: longConfig,
|
||||||
|
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
viper.AutomaticEnv()
|
||||||
|
|
||||||
|
_ = viper.BindPFlag("driver.config.configmap", cmd.Flags().Lookup("configmap"))
|
||||||
|
_ = viper.BindPFlag("driver.config.namespace", cmd.Flags().Lookup("namespace"))
|
||||||
|
_ = viper.BindPFlag("driver.config.update_falco", cmd.Flags().Lookup("update-falco"))
|
||||||
|
_ = viper.BindPFlag("driver.config.kubeconfig", cmd.Flags().Lookup("kubeconfig"))
|
||||||
|
_ = viper.BindPFlag("driver.config.configdir", cmd.Flags().Lookup("falco-config-dir"))
|
||||||
|
|
||||||
|
o.configmap = viper.GetString("driver.config.configmap")
|
||||||
|
o.namespace = viper.GetString("driver.config.namespace")
|
||||||
|
o.kubeconfig = viper.GetString("driver.config.kubeconfig")
|
||||||
|
o.update = viper.GetBool("driver.config.update_falco")
|
||||||
|
o.configDir = viper.GetString("driver.config.configdir")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return o.RunDriverConfig(ctx)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().BoolVar(&o.update, "update-falco", true, "Whether to overwrite Falco configuration")
|
||||||
|
cmd.Flags().StringVar(&o.namespace, "namespace", "", "Kubernetes namespace.")
|
||||||
|
cmd.Flags().StringVar(&o.kubeconfig, "kubeconfig", "", "Kubernetes config.")
|
||||||
|
cmd.Flags().StringVar(&o.configmap, "configmap", "", "Falco configmap name.")
|
||||||
|
cmd.Flags().StringVar(&o.configDir, "falco-config-dir", "/etc/falco", "Falco configuration directory.")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunDriverConfig implements the driver configuration command.
|
||||||
|
func (o *driverConfigOptions) RunDriverConfig(ctx context.Context) error {
|
||||||
|
o.Printer.Logger.Info("Running falcoctl driver config", o.Printer.Logger.Args(
|
||||||
|
"name", o.Driver.Name,
|
||||||
|
"version", o.Driver.Version,
|
||||||
|
"type", o.Driver.Type.String(),
|
||||||
|
"host-root", o.Driver.HostRoot,
|
||||||
|
"repos", strings.Join(o.Driver.Repos, ",")))
|
||||||
|
|
||||||
|
if o.update {
|
||||||
|
var cl kubernetes.Interface
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if o.namespace != "" {
|
||||||
|
// Create a new clientset.
|
||||||
|
if cl, err = setupClient(o.kubeconfig); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := o.Commit(ctx, cl, o.Driver.Type); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o.Printer.Logger.Info("Storing falcoctl driver config")
|
||||||
|
return config.StoreDriver(o.Driver.ToDriverConfig(), o.ConfigFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkFalcoRunsWithDrivers(engineKind string) bool {
|
||||||
|
// Modify the data in the ConfigMap/Falco config file ONLY if engine.kind is set to a known driver type.
|
||||||
|
// This ensures that we modify the config only for Falcos running with drivers, and not plugins/gvisor.
|
||||||
|
// Scenario: user has multiple Falco pods deployed in its cluster, one running with driver,
|
||||||
|
// other running with plugins. We must only touch the one running with driver.
|
||||||
|
if _, err := drivertype.Parse(engineKind); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *driverConfigOptions) IsRunningInDriverModeHost() (bool, error) {
|
||||||
|
o.Printer.Logger.Debug("Checking if Falco is running in driver mode on host system")
|
||||||
|
|
||||||
|
falcoCfgFile := filepath.Join(o.configDir, falcoConfigFile)
|
||||||
|
yamlFile, err := os.ReadFile(filepath.Clean(falcoCfgFile))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
cfg := falcoCfg{}
|
||||||
|
if err = yaml.Unmarshal(yamlFile, &cfg); err != nil {
|
||||||
|
return false, fmt.Errorf("unable to unmarshal falco.yaml to falcoCfg struct: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return checkFalcoRunsWithDrivers(cfg.Engine.Kind), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *driverConfigOptions) IsRunningInDriverModeK8S(ctx context.Context, cl kubernetes.Interface) (bool, error) {
|
||||||
|
o.Printer.Logger.Debug("Checking if Falco is running in driver mode in Kubernetes")
|
||||||
|
|
||||||
|
configMap, err := cl.CoreV1().ConfigMaps(o.namespace).Get(ctx, o.configmap, metav1.GetOptions{})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("unable to get configmap %s in namespace %s: %w", o.configmap, o.namespace, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that this is a Falco config map
|
||||||
|
falcoYaml, present := configMap.Data["falco.yaml"]
|
||||||
|
if !present {
|
||||||
|
o.Printer.Logger.Debug("Skip non Falco-related config map",
|
||||||
|
o.Printer.Logger.Args("configMap", configMap.Name))
|
||||||
|
return false, fmt.Errorf("configMap %s does not contain key \"falco.yaml\"", o.configmap)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that Falco is configured to run with a driver
|
||||||
|
var falcoConfig falcoCfg
|
||||||
|
err = yaml.Unmarshal([]byte(falcoYaml), &falcoConfig)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("unable to unmarshal falco.yaml to falcoCfg struct: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return checkFalcoRunsWithDrivers(falcoConfig.Engine.Kind), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit saves the updated driver type to Falco config,
|
||||||
|
// in a specialized configuration file under /etc/falco/config.d.
|
||||||
|
func (o *driverConfigOptions) Commit(ctx context.Context, cl kubernetes.Interface, driverType drivertype.DriverType) error {
|
||||||
|
// If set to true, then we need to overwrite the driver type.
|
||||||
|
var overwrite bool
|
||||||
|
var err error
|
||||||
|
if cl != nil {
|
||||||
|
if overwrite, err = o.IsRunningInDriverModeK8S(ctx, cl); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if overwrite, err = o.IsRunningInDriverModeHost(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if overwrite {
|
||||||
|
o.Printer.Logger.Info("Committing driver config to specialized configuration file under",
|
||||||
|
o.Printer.Logger.Args("directory", filepath.Join(o.configDir, "config.d")))
|
||||||
|
return overwriteDriverType(o.configDir, driverType)
|
||||||
|
}
|
||||||
|
|
||||||
|
o.Printer.Logger.Info("Falco is not configured to run with a driver, no need to set driver type.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupClient(kubeconfig string) (kubernetes.Interface, error) {
|
||||||
|
var cfg *rest.Config
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Create the rest config.
|
||||||
|
if kubeconfig != "" {
|
||||||
|
cfg, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
|
||||||
|
} else {
|
||||||
|
cfg, err = rest.InClusterConfig()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the clientset.
|
||||||
|
return kubernetes.NewForConfig(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func overwriteDriverType(configDir string, driverType drivertype.DriverType) error {
|
||||||
|
var falcoConfig falcoCfg
|
||||||
|
|
||||||
|
configDir = filepath.Join(configDir, "config.d")
|
||||||
|
// First thing, check if config.d folder exists in the configuration directory.
|
||||||
|
_, err := os.Stat(configDir)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
// Create it.
|
||||||
|
// #nosec G301 -- under /etc we want 755 permissions
|
||||||
|
if err := os.MkdirAll(configDir, 0o755); err != nil {
|
||||||
|
return fmt.Errorf("unable to create directory %s: %w", configDir, err)
|
||||||
|
}
|
||||||
|
} else if err != nil && !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
falcoConfig.Engine.Kind = driverType.String()
|
||||||
|
engineKind, err := yaml.Marshal(falcoConfig)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to marshal falco config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the engine configuration to a specialized config file.
|
||||||
|
// #nosec G306 //under /etc we want 644 permissions
|
||||||
|
if err := os.WriteFile(filepath.Join(configDir, falcoDriverConfigFile), engineKind, 0o644); err != nil {
|
||||||
|
return fmt.Errorf("unable to persist engine kind to filesystem: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package driverconfig_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/onsi/gomega/gbytes"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/falcosecurity/falcoctl/cmd"
|
||||||
|
commonoptions "github.com/falcosecurity/falcoctl/pkg/options"
|
||||||
|
testutils "github.com/falcosecurity/falcoctl/pkg/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ctx = context.Background()
|
||||||
|
output = gbytes.NewBuffer()
|
||||||
|
rootCmd *cobra.Command
|
||||||
|
opt *commonoptions.Common
|
||||||
|
configFile string
|
||||||
|
err error
|
||||||
|
args []string
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfig(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Config Suite")
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = BeforeSuite(func() {
|
||||||
|
|
||||||
|
// Create and configure the common options.
|
||||||
|
opt = commonoptions.NewOptions()
|
||||||
|
opt.Initialize(commonoptions.WithWriter(output))
|
||||||
|
|
||||||
|
// Create temporary directory used to save the configuration file.
|
||||||
|
configFile, err = testutils.CreateEmptyFile("falcoctl.yaml")
|
||||||
|
Expect(err).Should(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
var _ = AfterSuite(func() {
|
||||||
|
configDir := filepath.Dir(configFile)
|
||||||
|
Expect(os.RemoveAll(configDir)).Should(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
func executeRoot(args []string) error {
|
||||||
|
rootCmd.SetArgs(args)
|
||||||
|
rootCmd.SetOut(output)
|
||||||
|
return cmd.Execute(rootCmd, opt)
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2024 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package driverconfig_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/onsi/gomega/gbytes"
|
||||||
|
|
||||||
|
"github.com/falcosecurity/falcoctl/cmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
//nolint:lll // no need to check for line length.
|
||||||
|
var driverConfigHelp = `Configure a driver for future usages with other driver subcommands.
|
||||||
|
It will also update local Falco configuration or k8s configmap depending on the environment where it is running, to let Falco use chosen driver.
|
||||||
|
Only supports deployments of Falco that use a driver engine, ie: one between kmod, ebpf and modern-ebpf.
|
||||||
|
If engine.kind key is set to a non-driver driven engine, Falco configuration won't be touched.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
falcoctl driver config [flags]
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--configmap string Falco configmap name.
|
||||||
|
--falco-config-dir string Falco configuration directory. (default "/etc/falco")
|
||||||
|
-h, --help help for config
|
||||||
|
--kubeconfig string Kubernetes config.
|
||||||
|
--namespace string Kubernetes namespace.
|
||||||
|
--update-falco Whether to overwrite Falco configuration (default true)
|
||||||
|
|
||||||
|
Global Flags:
|
||||||
|
--config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml")
|
||||||
|
--host-root string Driver host root to be used. (default "/")
|
||||||
|
--kernelrelease string Specify the kernel release for which to download/build the driver in the same format used by 'uname -r' (e.g. '6.1.0-10-cloud-amd64')
|
||||||
|
--kernelversion string Specify the kernel version for which to download/build the driver in the same format used by 'uname -v' (e.g. '#1 SMP PREEMPT_DYNAMIC Debian 6.1.38-2 (2023-07-27)')
|
||||||
|
--log-format string Set formatting for logs (color, text, json) (default "color")
|
||||||
|
--log-level string Set level for logs (info, warn, debug, trace) (default "info")
|
||||||
|
--name string Driver name to be used. (default "falco")
|
||||||
|
--repo strings Driver repo to be used. (default [https://download.falco.org/driver])
|
||||||
|
--type strings Driver types allowed in descending priority order (ebpf, kmod, modern_ebpf) (default [modern_ebpf,kmod,ebpf])
|
||||||
|
--version string Driver version to be used.
|
||||||
|
`
|
||||||
|
|
||||||
|
var addAssertFailedBehavior = func(specificError string) {
|
||||||
|
It("check that fails and the usage is not printed", func() {
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(specificError)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Describe("config", func() {
|
||||||
|
|
||||||
|
var (
|
||||||
|
driverCmd = "driver"
|
||||||
|
configCmd = "config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Each test gets its own root command and runs it.
|
||||||
|
// The err variable is asserted by each test.
|
||||||
|
JustBeforeEach(func() {
|
||||||
|
rootCmd = cmd.New(ctx, opt)
|
||||||
|
err = executeRoot(args)
|
||||||
|
})
|
||||||
|
|
||||||
|
JustAfterEach(func() {
|
||||||
|
Expect(output.Clear()).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("help message", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{driverCmd, configCmd, "--help"}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should match the saved one", func() {
|
||||||
|
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(driverConfigHelp)))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Here we are testing failure cases for configuring a driver.
|
||||||
|
Context("failure", func() {
|
||||||
|
When("with non absolute host-root", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{driverCmd, configCmd, "--config", configFile, "--host-root", "foo/"}
|
||||||
|
})
|
||||||
|
addAssertFailedBehavior("ERROR host-root must be an absolute path (foo/)")
|
||||||
|
})
|
||||||
|
|
||||||
|
When("with invalid driver type", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{driverCmd, configCmd, "--config", configFile, "--type", "foo"}
|
||||||
|
})
|
||||||
|
addAssertFailedBehavior(`ERROR unsupported driver type specified: foo`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,17 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package driverconfig defines the configure logic for the driver cmd.
|
||||||
|
package driverconfig
|
|
@ -0,0 +1,241 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2024 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build linux
|
||||||
|
|
||||||
|
// Package driver implements the driver related cmd line interface.
|
||||||
|
package driver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/blang/semver"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
|
drivercleanup "github.com/falcosecurity/falcoctl/cmd/driver/cleanup"
|
||||||
|
driverconfig "github.com/falcosecurity/falcoctl/cmd/driver/config"
|
||||||
|
driverinstall "github.com/falcosecurity/falcoctl/cmd/driver/install"
|
||||||
|
driverprintenv "github.com/falcosecurity/falcoctl/cmd/driver/printenv"
|
||||||
|
"github.com/falcosecurity/falcoctl/internal/config"
|
||||||
|
driverdistro "github.com/falcosecurity/falcoctl/pkg/driver/distro"
|
||||||
|
driverkernel "github.com/falcosecurity/falcoctl/pkg/driver/kernel"
|
||||||
|
drivertype "github.com/falcosecurity/falcoctl/pkg/driver/type"
|
||||||
|
"github.com/falcosecurity/falcoctl/pkg/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewDriverCmd returns the driver command.
|
||||||
|
func NewDriverCmd(ctx context.Context, opt *options.Common) *cobra.Command {
|
||||||
|
driver := &options.Driver{}
|
||||||
|
driverTypesEnum := options.NewDriverTypes()
|
||||||
|
var (
|
||||||
|
driverTypesStr []string
|
||||||
|
driverKernelRelease string
|
||||||
|
driverKernelVersion string
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "driver",
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
|
Short: "Interact with falcosecurity driver",
|
||||||
|
Long: `Interact with falcosecurity driver.`,
|
||||||
|
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
opt.Initialize()
|
||||||
|
if err := config.Load(opt.ConfigFile); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override "version" flag with viper config if not set by user.
|
||||||
|
f := cmd.Flags().Lookup("version")
|
||||||
|
if f == nil {
|
||||||
|
// should never happen
|
||||||
|
return fmt.Errorf("unable to retrieve flag version")
|
||||||
|
} else if !f.Changed && viper.IsSet(config.DriverVersionKey) {
|
||||||
|
val := viper.Get(config.DriverVersionKey)
|
||||||
|
if err := cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val)); err != nil {
|
||||||
|
return fmt.Errorf("unable to overwrite \"version\" flag: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override "repo" flag with viper config if not set by user.
|
||||||
|
f = cmd.Flags().Lookup("repo")
|
||||||
|
if f == nil {
|
||||||
|
// should never happen
|
||||||
|
return fmt.Errorf("unable to retrieve flag repo")
|
||||||
|
} else if !f.Changed && viper.IsSet(config.DriverReposKey) {
|
||||||
|
val, err := config.DriverRepos()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := cmd.Flags().Set(f.Name, strings.Join(val, ",")); err != nil {
|
||||||
|
return fmt.Errorf("unable to overwrite \"repo\" flag: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override "name" flag with viper config if not set by user.
|
||||||
|
f = cmd.Flags().Lookup("name")
|
||||||
|
if f == nil {
|
||||||
|
// should never happen
|
||||||
|
return fmt.Errorf("unable to retrieve flag name")
|
||||||
|
} else if !f.Changed && viper.IsSet(config.DriverNameKey) {
|
||||||
|
val := viper.Get(config.DriverNameKey)
|
||||||
|
if err := cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val)); err != nil {
|
||||||
|
return fmt.Errorf("unable to overwrite \"name\" flag: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override "host-root" flag with viper config if not set by user.
|
||||||
|
f = cmd.Flags().Lookup("host-root")
|
||||||
|
if f == nil {
|
||||||
|
// should never happen
|
||||||
|
return fmt.Errorf("unable to retrieve flag host-root")
|
||||||
|
} else if !f.Changed && viper.IsSet(config.DriverHostRootKey) {
|
||||||
|
val := viper.Get(config.DriverHostRootKey)
|
||||||
|
if err := cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val)); err != nil {
|
||||||
|
return fmt.Errorf("unable to overwrite \"host-root\" flag: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override "type" flag with viper config if not set by user.
|
||||||
|
f = cmd.Flags().Lookup("type")
|
||||||
|
if f == nil {
|
||||||
|
// should never happen
|
||||||
|
return fmt.Errorf("unable to retrieve flag type")
|
||||||
|
} else if !f.Changed && viper.IsSet(config.DriverTypeKey) {
|
||||||
|
val, err := config.DriverTypes()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := cmd.Flags().Set(f.Name, strings.Join(val, ",")); err != nil {
|
||||||
|
return fmt.Errorf("unable to overwrite \"type\" flag: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logic to discover correct driver to be used
|
||||||
|
// Step 1: build up allowed driver types
|
||||||
|
allowedDriverTypes := make([]drivertype.DriverType, 0)
|
||||||
|
for _, dTypeStr := range driverTypesStr {
|
||||||
|
// Ok driver type was enforced by the user
|
||||||
|
drvType, err := drivertype.Parse(dTypeStr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
allowedDriverTypes = append(allowedDriverTypes, drvType)
|
||||||
|
opt.Printer.Logger.Debug("Allowed driver",
|
||||||
|
opt.Printer.Logger.Args("type", drvType))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: fetch system info (kernel release/version and distro)
|
||||||
|
var err error
|
||||||
|
driver.Kr, err = driverkernel.FetchInfo(driverKernelRelease, driverKernelVersion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
opt.Printer.Logger.Debug("Fetched kernel info", opt.Printer.Logger.Args(
|
||||||
|
"arch", driver.Kr.Architecture.ToNonDeb(),
|
||||||
|
"kernel release", driver.Kr.String(),
|
||||||
|
"kernel version", driver.Kr.KernelVersion))
|
||||||
|
|
||||||
|
driver.Distro, err = driverdistro.Discover(driver.Kr, driver.HostRoot)
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, driverdistro.ErrUnsupported) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
opt.Printer.Logger.Debug("Detected an unsupported target system; falling back at generic logic.")
|
||||||
|
}
|
||||||
|
opt.Printer.Logger.Debug("Discovered distro", opt.Printer.Logger.Args("target", driver.Distro))
|
||||||
|
|
||||||
|
driver.Type = driver.Distro.PreferredDriver(driver.Kr, allowedDriverTypes)
|
||||||
|
if driver.Type == nil {
|
||||||
|
return fmt.Errorf("no supported driver found for distro: %s, "+
|
||||||
|
"kernelrelease %s, "+
|
||||||
|
"kernelversion %s, "+
|
||||||
|
"arch %s",
|
||||||
|
driver.Distro.String(),
|
||||||
|
driver.Kr.String(),
|
||||||
|
driver.Kr.KernelVersion,
|
||||||
|
driver.Kr.Architecture.ToNonDeb())
|
||||||
|
}
|
||||||
|
opt.Printer.Logger.Debug("Detected supported driver", opt.Printer.Logger.Args("type", driver.Type.String()))
|
||||||
|
|
||||||
|
// If empty, try to load it automatically from /usr/src sub folders,
|
||||||
|
// using the most recent (ie: the one with greatest semver) driver version.
|
||||||
|
if driver.Version == "" {
|
||||||
|
driver.Version = loadDriverVersion()
|
||||||
|
}
|
||||||
|
return driver.Validate()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.PersistentFlags().StringSliceVar(&driverTypesStr, "type", config.DefaultDriver.Type,
|
||||||
|
"Driver types allowed in descending priority order "+driverTypesEnum.Allowed())
|
||||||
|
cmd.PersistentFlags().StringVar(&driver.Version, "version", config.DefaultDriver.Version, "Driver version to be used.")
|
||||||
|
cmd.PersistentFlags().StringSliceVar(&driver.Repos, "repo", config.DefaultDriver.Repos, "Driver repo to be used.")
|
||||||
|
cmd.PersistentFlags().StringVar(&driver.Name, "name", config.DefaultDriver.Name, "Driver name to be used.")
|
||||||
|
cmd.PersistentFlags().StringVar(&driver.HostRoot, "host-root", config.DefaultDriver.HostRoot, "Driver host root to be used.")
|
||||||
|
cmd.PersistentFlags().StringVar(&driverKernelRelease,
|
||||||
|
"kernelrelease",
|
||||||
|
"",
|
||||||
|
"Specify the kernel release for which to download/build the driver in the same format used by 'uname -r' "+
|
||||||
|
"(e.g. '6.1.0-10-cloud-amd64')")
|
||||||
|
cmd.PersistentFlags().StringVar(&driverKernelVersion,
|
||||||
|
"kernelversion",
|
||||||
|
"",
|
||||||
|
"Specify the kernel version for which to download/build the driver in the same format used by 'uname -v' "+
|
||||||
|
"(e.g. '#1 SMP PREEMPT_DYNAMIC Debian 6.1.38-2 (2023-07-27)')")
|
||||||
|
|
||||||
|
cmd.AddCommand(driverinstall.NewDriverInstallCmd(ctx, opt, driver))
|
||||||
|
cmd.AddCommand(driverconfig.NewDriverConfigCmd(ctx, opt, driver))
|
||||||
|
cmd.AddCommand(drivercleanup.NewDriverCleanupCmd(ctx, opt, driver))
|
||||||
|
cmd.AddCommand(driverprintenv.NewDriverPrintenvCmd(ctx, opt, driver))
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadDriverVersion() string {
|
||||||
|
isSet := false
|
||||||
|
greatestVrs := semver.Version{}
|
||||||
|
paths, _ := filepath.Glob("/usr/src/falco-*")
|
||||||
|
for _, path := range paths {
|
||||||
|
fileInfo, err := os.Stat(path)
|
||||||
|
// We expect path to point to a folder,
|
||||||
|
// otherwise skip it.
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !fileInfo.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
drvVer := strings.TrimPrefix(filepath.Base(path), "falco-")
|
||||||
|
sv, err := semver.Parse(drvVer)
|
||||||
|
if err != nil {
|
||||||
|
// Not a semver; return it because we
|
||||||
|
// Won't be able to check it against semver driver versions.
|
||||||
|
return drvVer
|
||||||
|
}
|
||||||
|
if sv.GT(greatestVrs) {
|
||||||
|
greatestVrs = sv
|
||||||
|
isSet = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isSet {
|
||||||
|
return greatestVrs.String()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2024 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build !linux
|
||||||
|
|
||||||
|
package driver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
|
commonoptions "github.com/falcosecurity/falcoctl/pkg/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewDriverCmd returns an empty driver command since it is not supported on non linuxes
|
||||||
|
func NewDriverCmd(ctx context.Context, opt *commonoptions.Common) *cobra.Command {
|
||||||
|
return &cobra.Command{}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package driverinstall defines the installation logic for the driver cmd.
|
||||||
|
package driverinstall
|
|
@ -0,0 +1,219 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2024 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package driverinstall
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pterm/pterm"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
|
driverdistro "github.com/falcosecurity/falcoctl/pkg/driver/distro"
|
||||||
|
"github.com/falcosecurity/falcoctl/pkg/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
type driverDownloadOptions struct {
|
||||||
|
InsecureDownload bool
|
||||||
|
HTTPTimeout time.Duration
|
||||||
|
HTTPHeaders string
|
||||||
|
}
|
||||||
|
|
||||||
|
type driverInstallOptions struct {
|
||||||
|
*options.Common
|
||||||
|
*options.Driver
|
||||||
|
Download bool
|
||||||
|
Compile bool
|
||||||
|
DownloadHeaders bool
|
||||||
|
driverDownloadOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDriverInstallCmd returns the driver install command.
|
||||||
|
func NewDriverInstallCmd(ctx context.Context, opt *options.Common, driver *options.Driver) *cobra.Command {
|
||||||
|
o := driverInstallOptions{
|
||||||
|
Common: opt,
|
||||||
|
Driver: driver,
|
||||||
|
// Defaults to downloading or building if needed
|
||||||
|
Download: true,
|
||||||
|
Compile: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "install [flags]",
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
|
Short: "Install previously configured driver",
|
||||||
|
Long: `Install previously configured driver, either downloading it or attempting a build.`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
dest, err := o.RunDriverInstall(ctx)
|
||||||
|
if dest != "" {
|
||||||
|
// We don't care about errors at this stage
|
||||||
|
// Fallback: try to load any available driver if leaving with an error.
|
||||||
|
// It is only useful for kmod, as it will try to
|
||||||
|
// modprobe a pre-existent version of the driver,
|
||||||
|
// hoping it will be compatible.
|
||||||
|
_ = driver.Type.Load(o.Printer, dest, o.Driver.Name, err != nil)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().BoolVar(&o.Download, "download", true, "Whether to enable download of prebuilt drivers")
|
||||||
|
cmd.Flags().BoolVar(&o.Compile, "compile", true, "Whether to enable local compilation of drivers")
|
||||||
|
cmd.Flags().BoolVar(&o.DownloadHeaders, "download-headers", true, "Whether to enable automatic kernel headers download where supported")
|
||||||
|
cmd.Flags().BoolVar(&o.InsecureDownload, "http-insecure", false, "Whether you want to allow insecure downloads or not")
|
||||||
|
cmd.Flags().DurationVar(&o.HTTPTimeout, "http-timeout", 60*time.Second, "Timeout for each http try")
|
||||||
|
cmd.Flags().StringVar(&o.HTTPHeaders, "http-headers",
|
||||||
|
"",
|
||||||
|
"Optional comma-separated list of headers for the http GET request "+
|
||||||
|
"(e.g. --http-headers='x-emc-namespace: default,Proxy-Authenticate: Basic'). Not necessary if default repo is used")
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:gosec // this was an existent option in falco-driver-loader that we are porting.
|
||||||
|
func setDefaultHTTPClientOpts(downloadOptions driverDownloadOptions) {
|
||||||
|
// Skip insecure verify
|
||||||
|
if downloadOptions.InsecureDownload {
|
||||||
|
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||||
|
}
|
||||||
|
http.DefaultClient.Timeout = downloadOptions.HTTPTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunDriverInstall implements the driver install command.
|
||||||
|
func (o *driverInstallOptions) RunDriverInstall(ctx context.Context) (string, error) {
|
||||||
|
o.Printer.Logger.Info("Running falcoctl driver install", o.Printer.Logger.Args(
|
||||||
|
"driver version", o.Driver.Version,
|
||||||
|
"driver type", o.Driver.Type,
|
||||||
|
"driver name", o.Driver.Name,
|
||||||
|
"compile", o.Compile,
|
||||||
|
"download", o.Download,
|
||||||
|
"target", o.Distro.String(),
|
||||||
|
"arch", o.Kr.Architecture.ToNonDeb(),
|
||||||
|
"kernel release", o.Kr.String(),
|
||||||
|
"kernel version", o.Kr.KernelVersion))
|
||||||
|
|
||||||
|
if !o.Driver.Type.HasArtifacts() {
|
||||||
|
o.Printer.Logger.Info("No artifacts needed for the selected driver.")
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !o.Download && !o.Compile {
|
||||||
|
o.Printer.Logger.Info("Nothing to do: download and compile disabled.")
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.Distro.String() == driverdistro.UndeterminedDistro {
|
||||||
|
if o.Compile {
|
||||||
|
o.Download = false
|
||||||
|
o.Printer.Logger.Info(
|
||||||
|
"Detected an unsupported target system, please get in touch with the Falco community. Trying to compile anyway.")
|
||||||
|
} else {
|
||||||
|
return "", fmt.Errorf("detected an unsupported target system, please get in touch with the Falco community")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
dest string
|
||||||
|
buf bytes.Buffer
|
||||||
|
)
|
||||||
|
|
||||||
|
if !o.Printer.DisableStyling {
|
||||||
|
o.Printer.Spinner, _ = o.Printer.Spinner.Start("Cleaning up existing drivers")
|
||||||
|
}
|
||||||
|
err := o.Driver.Type.Cleanup(o.Printer.WithWriter(&buf), o.Driver.Name)
|
||||||
|
if o.Printer.Spinner != nil {
|
||||||
|
_ = o.Printer.Spinner.Stop()
|
||||||
|
}
|
||||||
|
if o.Printer.Logger.Formatter == pterm.LogFormatterJSON {
|
||||||
|
// Only print formatted text if we are formatting to json
|
||||||
|
out := strings.ReplaceAll(buf.String(), "\n", ";")
|
||||||
|
o.Printer.Logger.Info("Driver cleanup", o.Printer.Logger.Args("output", out))
|
||||||
|
} else {
|
||||||
|
// Print much more readable output as-is
|
||||||
|
o.Printer.DefaultText.Print(buf.String())
|
||||||
|
}
|
||||||
|
buf.Reset()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.Download {
|
||||||
|
setDefaultHTTPClientOpts(o.driverDownloadOptions)
|
||||||
|
if !o.Printer.DisableStyling {
|
||||||
|
o.Printer.Spinner, _ = o.Printer.Spinner.Start("Trying to download the driver")
|
||||||
|
}
|
||||||
|
dest, err = driverdistro.Download(ctx, o.Distro, o.Printer.WithWriter(&buf), o.Kr, o.Driver.Name,
|
||||||
|
o.Driver.Type, o.Driver.Version, o.Driver.Repos, o.HTTPHeaders)
|
||||||
|
if o.Printer.Spinner != nil {
|
||||||
|
_ = o.Printer.Spinner.Stop()
|
||||||
|
}
|
||||||
|
if o.Printer.Logger.Formatter == pterm.LogFormatterJSON {
|
||||||
|
// Only print formatted text if we are formatting to json
|
||||||
|
out := strings.ReplaceAll(buf.String(), "\n", ";")
|
||||||
|
o.Printer.Logger.Info("Driver download", o.Printer.Logger.Args("output", out))
|
||||||
|
} else {
|
||||||
|
// Print much more readable output as-is
|
||||||
|
o.Printer.DefaultText.Print(buf.String())
|
||||||
|
}
|
||||||
|
buf.Reset()
|
||||||
|
if err == nil {
|
||||||
|
o.Printer.Logger.Info("Driver downloaded.", o.Printer.Logger.Args("path", dest))
|
||||||
|
return dest, nil
|
||||||
|
}
|
||||||
|
if errors.Is(err, driverdistro.ErrAlreadyPresent) {
|
||||||
|
o.Printer.Logger.Info("Skipping download, driver already present.", o.Printer.Logger.Args("path", dest))
|
||||||
|
return dest, nil
|
||||||
|
}
|
||||||
|
// Print the error but go on
|
||||||
|
// attempting a build if requested
|
||||||
|
if o.Compile {
|
||||||
|
o.Printer.Logger.Warn(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.Compile {
|
||||||
|
if !o.Printer.DisableStyling {
|
||||||
|
o.Printer.Spinner, _ = o.Printer.Spinner.Start("Trying to build the driver")
|
||||||
|
}
|
||||||
|
dest, err = driverdistro.Build(ctx, o.Distro, o.Printer.WithWriter(&buf), o.Kr, o.Driver.Name, o.Driver.Type, o.Driver.Version, o.DownloadHeaders)
|
||||||
|
if o.Printer.Spinner != nil {
|
||||||
|
_ = o.Printer.Spinner.Stop()
|
||||||
|
}
|
||||||
|
if o.Printer.Logger.Formatter == pterm.LogFormatterJSON {
|
||||||
|
// Only print formatted text if we are formatting to json
|
||||||
|
out := strings.ReplaceAll(buf.String(), "\n", ";")
|
||||||
|
o.Printer.Logger.Info("Driver build", o.Printer.Logger.Args("output", out))
|
||||||
|
} else {
|
||||||
|
// Print much more readable output as-is
|
||||||
|
o.Printer.DefaultText.Print(buf.String())
|
||||||
|
}
|
||||||
|
buf.Reset()
|
||||||
|
if err == nil {
|
||||||
|
return dest, nil
|
||||||
|
}
|
||||||
|
if errors.Is(err, driverdistro.ErrAlreadyPresent) {
|
||||||
|
o.Printer.Logger.Info("Skipping build, driver already present.", o.Printer.Logger.Args("path", dest))
|
||||||
|
return dest, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return o.Driver.Name, fmt.Errorf("failed: %w", err)
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package driverinstall_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/onsi/gomega/gbytes"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/falcosecurity/falcoctl/cmd"
|
||||||
|
commonoptions "github.com/falcosecurity/falcoctl/pkg/options"
|
||||||
|
testutils "github.com/falcosecurity/falcoctl/pkg/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ctx = context.Background()
|
||||||
|
output = gbytes.NewBuffer()
|
||||||
|
rootCmd *cobra.Command
|
||||||
|
opt *commonoptions.Common
|
||||||
|
configFile string
|
||||||
|
err error
|
||||||
|
args []string
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInstall(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Install Suite")
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = BeforeSuite(func() {
|
||||||
|
|
||||||
|
// Create and configure the common options.
|
||||||
|
opt = commonoptions.NewOptions()
|
||||||
|
opt.Initialize(commonoptions.WithWriter(output))
|
||||||
|
|
||||||
|
// Create temporary directory used to save the configuration file.
|
||||||
|
configFile, err = testutils.CreateEmptyFile("falcoctl.yaml")
|
||||||
|
Expect(err).Should(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
var _ = AfterSuite(func() {
|
||||||
|
configDir := filepath.Dir(configFile)
|
||||||
|
Expect(os.RemoveAll(configDir)).Should(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
func executeRoot(args []string) error {
|
||||||
|
rootCmd.SetArgs(args)
|
||||||
|
rootCmd.SetOut(output)
|
||||||
|
return cmd.Execute(rootCmd, opt)
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2024 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package driverinstall_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/onsi/gomega/gbytes"
|
||||||
|
|
||||||
|
"github.com/falcosecurity/falcoctl/cmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
//nolint:lll // no need to check for line length.
|
||||||
|
var driverInstallHelp = `Install previously configured driver, either downloading it or attempting a build.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
falcoctl driver install [flags]
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--compile Whether to enable local compilation of drivers (default true)
|
||||||
|
--download Whether to enable download of prebuilt drivers (default true)
|
||||||
|
--download-headers Whether to enable automatic kernel headers download where supported (default true)
|
||||||
|
-h, --help help for install
|
||||||
|
--http-headers string Optional comma-separated list of headers for the http GET request (e.g. --http-headers='x-emc-namespace: default,Proxy-Authenticate: Basic'). Not necessary if default repo is used
|
||||||
|
--http-insecure Whether you want to allow insecure downloads or not
|
||||||
|
--http-timeout duration Timeout for each http try (default 1m0s)
|
||||||
|
|
||||||
|
Global Flags:
|
||||||
|
--config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml")
|
||||||
|
--host-root string Driver host root to be used. (default "/")
|
||||||
|
--kernelrelease string Specify the kernel release for which to download/build the driver in the same format used by 'uname -r' (e.g. '6.1.0-10-cloud-amd64')
|
||||||
|
--kernelversion string Specify the kernel version for which to download/build the driver in the same format used by 'uname -v' (e.g. '#1 SMP PREEMPT_DYNAMIC Debian 6.1.38-2 (2023-07-27)')
|
||||||
|
--log-format string Set formatting for logs (color, text, json) (default "color")
|
||||||
|
--log-level string Set level for logs (info, warn, debug, trace) (default "info")
|
||||||
|
--name string Driver name to be used. (default "falco")
|
||||||
|
--repo strings Driver repo to be used. (default [https://download.falco.org/driver])
|
||||||
|
--type strings Driver types allowed in descending priority order (ebpf, kmod, modern_ebpf) (default [modern_ebpf,kmod,ebpf])
|
||||||
|
--version string Driver version to be used.
|
||||||
|
`
|
||||||
|
|
||||||
|
var addAssertFailedBehavior = func(specificError string) {
|
||||||
|
It("check that fails and the usage is not printed", func() {
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(specificError)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var addAssertOkBehavior = func(specificOut string) {
|
||||||
|
It("check that does not fail and the usage is not printed", func() {
|
||||||
|
Succeed()
|
||||||
|
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(specificOut)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Describe("install", func() {
|
||||||
|
|
||||||
|
var (
|
||||||
|
driverCmd = "driver"
|
||||||
|
installCmd = "install"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Each test gets its own root command and runs it.
|
||||||
|
// The err variable is asserted by each test.
|
||||||
|
JustBeforeEach(func() {
|
||||||
|
rootCmd = cmd.New(ctx, opt)
|
||||||
|
err = executeRoot(args)
|
||||||
|
})
|
||||||
|
|
||||||
|
JustAfterEach(func() {
|
||||||
|
Expect(output.Clear()).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("help message", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{driverCmd, installCmd, "--help"}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should match the saved one", func() {
|
||||||
|
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(driverInstallHelp)))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Here we are testing failure cases for installing a driver.
|
||||||
|
Context("failure", func() {
|
||||||
|
When("with empty driver version", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{driverCmd, installCmd, "--config", configFile}
|
||||||
|
})
|
||||||
|
addAssertFailedBehavior(`ERROR version is mandatory and cannot be empty`)
|
||||||
|
})
|
||||||
|
|
||||||
|
When("with non absolute host-root", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{driverCmd, installCmd, "--config", configFile, "--host-root", "foo/", "--version", "1.0.0+driver"}
|
||||||
|
})
|
||||||
|
addAssertFailedBehavior("ERROR host-root must be an absolute path (foo/)")
|
||||||
|
})
|
||||||
|
|
||||||
|
When("with invalid driver type", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{driverCmd, installCmd, "--config", configFile, "--type", "foo", "--version", "1.0.0+driver"}
|
||||||
|
})
|
||||||
|
addAssertFailedBehavior(`ERROR unsupported driver type specified: foo`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("nothing-to-do", func() {
|
||||||
|
When("with false download and compile", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{driverCmd, installCmd, "--config", configFile, "--download=false", "--compile=false", "--version", "1.0.0+driver"}
|
||||||
|
})
|
||||||
|
addAssertOkBehavior("INFO Nothing to do: download and compile disabled.")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,17 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package driverprintenv defines the logic to print driver-related variables as env vars.
|
||||||
|
package driverprintenv
|
|
@ -0,0 +1,65 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2024 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package driverprintenv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
|
"github.com/falcosecurity/falcoctl/pkg/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
type driverPrintenvOptions struct {
|
||||||
|
*options.Common
|
||||||
|
*options.Driver
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDriverPrintenvCmd print info about driver falcoctl config as env vars.
|
||||||
|
func NewDriverPrintenvCmd(ctx context.Context, opt *options.Common, driver *options.Driver) *cobra.Command {
|
||||||
|
o := driverPrintenvOptions{
|
||||||
|
Common: opt,
|
||||||
|
Driver: driver,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "printenv [flags]",
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
|
Short: "Print env vars",
|
||||||
|
Long: `Print variables used by driver as env vars.`,
|
||||||
|
RunE: func(_ *cobra.Command, _ []string) error {
|
||||||
|
return o.RunDriverPrintenv(ctx)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *driverPrintenvOptions) RunDriverPrintenv(_ context.Context) error {
|
||||||
|
o.Printer.DefaultText.Printf("DRIVER=%q\n", o.Driver.Type.String())
|
||||||
|
o.Printer.DefaultText.Printf("DRIVERS_REPO=%q\n", strings.Join(o.Driver.Repos, ", "))
|
||||||
|
o.Printer.DefaultText.Printf("DRIVER_VERSION=%q\n", o.Driver.Version)
|
||||||
|
o.Printer.DefaultText.Printf("DRIVER_NAME=%q\n", o.Driver.Name)
|
||||||
|
o.Printer.DefaultText.Printf("HOST_ROOT=%q\n", o.Driver.HostRoot)
|
||||||
|
o.Printer.DefaultText.Printf("TARGET_ID=%q\n", o.Distro.String())
|
||||||
|
o.Printer.DefaultText.Printf("ARCH=%q\n", o.Kr.Architecture.ToNonDeb())
|
||||||
|
o.Printer.DefaultText.Printf("KERNEL_RELEASE=%q\n", o.Kr.String())
|
||||||
|
o.Printer.DefaultText.Printf("KERNEL_VERSION=%q\n", o.Kr.KernelVersion)
|
||||||
|
fixedKr := o.Distro.FixupKernel(o.Kr)
|
||||||
|
o.Printer.DefaultText.Printf("FIXED_KERNEL_RELEASE=%q\n", fixedKr.String())
|
||||||
|
o.Printer.DefaultText.Printf("FIXED_KERNEL_VERSION=%q\n", fixedKr.KernelVersion)
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package driverprintenv_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/onsi/gomega/gbytes"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/falcosecurity/falcoctl/cmd"
|
||||||
|
commonoptions "github.com/falcosecurity/falcoctl/pkg/options"
|
||||||
|
testutils "github.com/falcosecurity/falcoctl/pkg/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ctx = context.Background()
|
||||||
|
output = gbytes.NewBuffer()
|
||||||
|
rootCmd *cobra.Command
|
||||||
|
opt *commonoptions.Common
|
||||||
|
configFile string
|
||||||
|
err error
|
||||||
|
args []string
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPrintenv(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Printenv Suite")
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = BeforeSuite(func() {
|
||||||
|
|
||||||
|
// Create and configure the common options.
|
||||||
|
opt = commonoptions.NewOptions()
|
||||||
|
opt.Initialize(commonoptions.WithWriter(output))
|
||||||
|
|
||||||
|
// Create temporary directory used to save the configuration file.
|
||||||
|
configFile, err = testutils.CreateEmptyFile("falcoctl.yaml")
|
||||||
|
Expect(err).Should(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
var _ = AfterSuite(func() {
|
||||||
|
configDir := filepath.Dir(configFile)
|
||||||
|
Expect(os.RemoveAll(configDir)).Should(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
func executeRoot(args []string) error {
|
||||||
|
rootCmd.SetArgs(args)
|
||||||
|
rootCmd.SetOut(output)
|
||||||
|
return cmd.Execute(rootCmd, opt)
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2024 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package driverprintenv_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/onsi/gomega/gbytes"
|
||||||
|
|
||||||
|
"github.com/falcosecurity/falcoctl/cmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
//nolint:lll // no need to check for line length.
|
||||||
|
var driverPrintenvHelp = `Print variables used by driver as env vars.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
falcoctl driver printenv [flags]
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-h, --help help for printenv
|
||||||
|
|
||||||
|
Global Flags:
|
||||||
|
--config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml")
|
||||||
|
--host-root string Driver host root to be used. (default "/")
|
||||||
|
--kernelrelease string Specify the kernel release for which to download/build the driver in the same format used by 'uname -r' (e.g. '6.1.0-10-cloud-amd64')
|
||||||
|
--kernelversion string Specify the kernel version for which to download/build the driver in the same format used by 'uname -v' (e.g. '#1 SMP PREEMPT_DYNAMIC Debian 6.1.38-2 (2023-07-27)')
|
||||||
|
--log-format string Set formatting for logs (color, text, json) (default "color")
|
||||||
|
--log-level string Set level for logs (info, warn, debug, trace) (default "info")
|
||||||
|
--name string Driver name to be used. (default "falco")
|
||||||
|
--repo strings Driver repo to be used. (default [https://download.falco.org/driver])
|
||||||
|
--type strings Driver types allowed in descending priority order (ebpf, kmod, modern_ebpf) (default [modern_ebpf,kmod,ebpf])
|
||||||
|
--version string Driver version to be used.
|
||||||
|
`
|
||||||
|
|
||||||
|
var driverPrintenvDefaultConfig = `DRIVER=".*"
|
||||||
|
DRIVERS_REPO="https:\/\/download\.falco\.org\/driver"
|
||||||
|
DRIVER_VERSION="1.0.0\+driver"
|
||||||
|
DRIVER_NAME="falco"
|
||||||
|
HOST_ROOT="\/"
|
||||||
|
TARGET_ID=".*"
|
||||||
|
ARCH="x86_64|aarch64"
|
||||||
|
KERNEL_RELEASE=".*"
|
||||||
|
KERNEL_VERSION=".*"
|
||||||
|
FIXED_KERNEL_RELEASE=".*"
|
||||||
|
FIXED_KERNEL_VERSION=".*"
|
||||||
|
`
|
||||||
|
|
||||||
|
var addAssertFailedBehavior = func(specificError string) {
|
||||||
|
It("check that fails and the usage is not printed", func() {
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(specificError)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Describe("printenv", func() {
|
||||||
|
|
||||||
|
var (
|
||||||
|
driverCmd = "driver"
|
||||||
|
printenvCmd = "printenv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Each test gets its own root command and runs it.
|
||||||
|
// The err variable is asserted by each test.
|
||||||
|
JustBeforeEach(func() {
|
||||||
|
rootCmd = cmd.New(ctx, opt)
|
||||||
|
err = executeRoot(args)
|
||||||
|
})
|
||||||
|
|
||||||
|
JustAfterEach(func() {
|
||||||
|
Expect(output.Clear()).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("help message", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{driverCmd, printenvCmd, "--help"}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should match the saved one", func() {
|
||||||
|
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(driverPrintenvHelp)))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Here we are testing failure cases for cleaning a driver.
|
||||||
|
Context("failure", func() {
|
||||||
|
When("with empty driver version", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{driverCmd, printenvCmd, "--config", configFile}
|
||||||
|
})
|
||||||
|
addAssertFailedBehavior(`ERROR version is mandatory and cannot be empty `)
|
||||||
|
})
|
||||||
|
|
||||||
|
When("with non absolute host-root", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{driverCmd, printenvCmd, "--config", configFile, "--host-root", "foo/", "--version", "1.0.0+driver"}
|
||||||
|
})
|
||||||
|
addAssertFailedBehavior("ERROR host-root must be an absolute path (foo/)")
|
||||||
|
})
|
||||||
|
|
||||||
|
When("with invalid driver type", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{driverCmd, printenvCmd, "--config", configFile, "--type", "foo", "--version", "1.0.0+driver"}
|
||||||
|
})
|
||||||
|
addAssertFailedBehavior(`unsupported driver type specified: foo`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("success", func() {
|
||||||
|
When("with default config values", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{driverCmd, printenvCmd, "--config", configFile, "--version", "1.0.0+driver"}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should match the saved one", func() {
|
||||||
|
Succeed()
|
||||||
|
MatchRegexp(driverPrintenvDefaultConfig)
|
||||||
|
Expect(string(output.Contents())).To(MatchRegexp(driverPrintenvDefaultConfig))
|
||||||
|
// Expect that output is bash setenv compatible
|
||||||
|
scanner := bufio.NewScanner(output)
|
||||||
|
for scanner.Scan() {
|
||||||
|
vals := strings.Split(scanner.Text(), "=")
|
||||||
|
Expect(vals).Should(HaveLen(2))
|
||||||
|
err := os.Setenv(vals[0], vals[1])
|
||||||
|
Expect(err).Should(BeNil())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2022 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -42,8 +43,6 @@ func NewIndexAddCmd(ctx context.Context, opt *options.Common) *cobra.Command {
|
||||||
Short: "Add an index to the local falcoctl configuration",
|
Short: "Add an index to the local falcoctl configuration",
|
||||||
Long: "Add an index to the local falcoctl configuration. Indexes are used to perform search operations for artifacts",
|
Long: "Add an index to the local falcoctl configuration. Indexes are used to perform search operations for artifacts",
|
||||||
Args: cobra.RangeArgs(2, 3),
|
Args: cobra.RangeArgs(2, 3),
|
||||||
SilenceErrors: true,
|
|
||||||
SilenceUsage: true,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return o.RunIndexAdd(ctx, args)
|
return o.RunIndexAdd(ctx, args)
|
||||||
},
|
},
|
||||||
|
@ -55,6 +54,7 @@ func NewIndexAddCmd(ctx context.Context, opt *options.Common) *cobra.Command {
|
||||||
// RunIndexAdd implements the index add command.
|
// RunIndexAdd implements the index add command.
|
||||||
func (o *IndexAddOptions) RunIndexAdd(ctx context.Context, args []string) error {
|
func (o *IndexAddOptions) RunIndexAdd(ctx context.Context, args []string) error {
|
||||||
var err error
|
var err error
|
||||||
|
logger := o.Printer.Logger
|
||||||
|
|
||||||
name := args[0]
|
name := args[0]
|
||||||
url := args[1]
|
url := args[1]
|
||||||
|
@ -63,24 +63,24 @@ func (o *IndexAddOptions) RunIndexAdd(ctx context.Context, args []string) error
|
||||||
backend = args[2]
|
backend = args[2]
|
||||||
}
|
}
|
||||||
|
|
||||||
o.Printer.Verbosef("Creating in-memory cache using indexes file %q and indexes directory %q", config.IndexesFile, config.IndexesDir)
|
logger.Debug("Creating in-memory cache using", logger.Args("indexes file", config.IndexesFile, "indexes directory", config.IndexesDir))
|
||||||
indexCache, err := cache.New(ctx, config.IndexesFile, config.IndexesDir)
|
indexCache, err := cache.New(ctx, config.IndexesFile, config.IndexesDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to create index cache: %w", err)
|
return fmt.Errorf("unable to create index cache: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
o.Printer.Info.Printfln("Adding index")
|
logger.Info("Adding index", logger.Args("name", name, "path", url))
|
||||||
|
|
||||||
if err = indexCache.Add(ctx, name, backend, url); err != nil {
|
if err = indexCache.Add(ctx, name, backend, url); err != nil {
|
||||||
return fmt.Errorf("unable to add index: %w", err)
|
return fmt.Errorf("unable to add index: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
o.Printer.Verbosef("Writing cache to disk")
|
logger.Debug("Writing cache to disk")
|
||||||
if _, err = indexCache.Write(); err != nil {
|
if _, err = indexCache.Write(); err != nil {
|
||||||
return fmt.Errorf("unable to write cache to disk: %w", err)
|
return fmt.Errorf("unable to write cache to disk: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
o.Printer.Verbosef("Adding new index entry to configuration file %q", o.ConfigFile)
|
logger.Debug("Adding new index entry to configuration", logger.Args("file", o.ConfigFile))
|
||||||
if err = config.AddIndexes([]config.Index{{
|
if err = config.AddIndexes([]config.Index{{
|
||||||
Name: name,
|
Name: name,
|
||||||
URL: url,
|
URL: url,
|
||||||
|
@ -89,7 +89,7 @@ func (o *IndexAddOptions) RunIndexAdd(ctx context.Context, args []string) error
|
||||||
return fmt.Errorf("index entry %q: %w", name, err)
|
return fmt.Errorf("index entry %q: %w", name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
o.Printer.Success.Printfln("Index %q successfully added", name)
|
logger.Info("Index successfully added")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2023 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -66,7 +67,6 @@ var _ = BeforeSuite(func() {
|
||||||
// Create and configure the common options.
|
// Create and configure the common options.
|
||||||
opt = commonoptions.NewOptions()
|
opt = commonoptions.NewOptions()
|
||||||
opt.Initialize(commonoptions.WithWriter(output))
|
opt.Initialize(commonoptions.WithWriter(output))
|
||||||
opt.Printer.DisableStylingf()
|
|
||||||
|
|
||||||
// Create temporary directory used to save the configuration file.
|
// Create temporary directory used to save the configuration file.
|
||||||
configFile, err = testutils.CreateEmptyFile("falcoctl.yaml")
|
configFile, err = testutils.CreateEmptyFile("falcoctl.yaml")
|
||||||
|
@ -83,5 +83,5 @@ var _ = AfterSuite(func() {
|
||||||
func executeRoot(args []string) error {
|
func executeRoot(args []string) error {
|
||||||
rootCmd.SetArgs(args)
|
rootCmd.SetArgs(args)
|
||||||
rootCmd.SetOut(output)
|
rootCmd.SetOut(output)
|
||||||
return cmd.Execute(rootCmd, opt.Printer)
|
return cmd.Execute(rootCmd, opt)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2023 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -33,8 +34,8 @@ Flags:
|
||||||
|
|
||||||
Global Flags:
|
Global Flags:
|
||||||
--config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml")
|
--config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml")
|
||||||
--disable-styling Disable output styling such as spinners, progress bars and colors. Styling is automatically disabled if not attacched to a tty (default false)
|
--log-format string Set formatting for logs (color, text, json) (default "color")
|
||||||
-v, --verbose Enable verbose logs (default false)
|
--log-level string Set level for logs (info, warn, debug, trace) (default "info")
|
||||||
`
|
`
|
||||||
|
|
||||||
//nolint:lll // no need to check for line length.
|
//nolint:lll // no need to check for line length.
|
||||||
|
@ -48,8 +49,8 @@ Flags:
|
||||||
|
|
||||||
Global Flags:
|
Global Flags:
|
||||||
--config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml")
|
--config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml")
|
||||||
--disable-styling Disable output styling such as spinners, progress bars and colors. Styling is automatically disabled if not attacched to a tty (default false)
|
--log-format string Set formatting for logs (color, text, json) (default "color")
|
||||||
-v, --verbose Enable verbose logs (default false)
|
--log-level string Set level for logs (info, warn, debug, trace) (default "info")
|
||||||
`
|
`
|
||||||
|
|
||||||
var addAssertFailedBehavior = func(usage, specificError string) {
|
var addAssertFailedBehavior = func(usage, specificError string) {
|
||||||
|
@ -96,14 +97,14 @@ var indexAddTests = Describe("add", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
args = []string{indexCmd, addCmd, "--config", configFile, indexName}
|
args = []string{indexCmd, addCmd, "--config", configFile, indexName}
|
||||||
})
|
})
|
||||||
addAssertFailedBehavior(indexAddUsage, "ERRO: accepts between 2 and 3 arg(s), received 1")
|
addAssertFailedBehavior(indexAddUsage, "ERROR accepts between 2 and 3 arg(s), received 1")
|
||||||
})
|
})
|
||||||
|
|
||||||
When("with invalid URL", func() {
|
When("with invalid URL", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
args = []string{indexCmd, addCmd, "--config", configFile, indexName, "NOTAPROTOCAL://something"}
|
args = []string{indexCmd, addCmd, "--config", configFile, indexName, "NOTAPROTOCAL://something"}
|
||||||
})
|
})
|
||||||
addAssertFailedBehavior(indexAddUsage, "ERRO: unable to add index: unable to fetch index \"testName\""+
|
addAssertFailedBehavior(indexAddUsage, "ERROR unable to add index: unable to fetch index \"testName\""+
|
||||||
" with URL \"NOTAPROTOCAL://something\": unable to fetch index: cannot fetch index: Get "+
|
" with URL \"NOTAPROTOCAL://something\": unable to fetch index: cannot fetch index: Get "+
|
||||||
"\"notaprotocal://something\": unsupported protocol scheme \"notaprotocal\"")
|
"\"notaprotocal://something\": unsupported protocol scheme \"notaprotocal\"")
|
||||||
})
|
})
|
||||||
|
@ -112,7 +113,7 @@ var indexAddTests = Describe("add", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
args = []string{indexCmd, addCmd, "--config", configFile, indexName, "http://noindex", "notabackend"}
|
args = []string{indexCmd, addCmd, "--config", configFile, indexName, "http://noindex", "notabackend"}
|
||||||
})
|
})
|
||||||
addAssertFailedBehavior(indexAddUsage, "ERRO: unable to add index: unable to fetch index \"testName\" "+
|
addAssertFailedBehavior(indexAddUsage, "ERROR unable to add index: unable to fetch index \"testName\" "+
|
||||||
"with URL \"http://noindex\": unsupported index backend type: notabackend")
|
"with URL \"http://noindex\": unsupported index backend type: notabackend")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2022 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2023 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2022 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -34,8 +35,6 @@ func NewIndexCmd(ctx context.Context, opt *commonoptions.Common) *cobra.Command
|
||||||
DisableFlagsInUseLine: true,
|
DisableFlagsInUseLine: true,
|
||||||
Short: "Interact with index",
|
Short: "Interact with index",
|
||||||
Long: "Interact with index",
|
Long: "Interact with index",
|
||||||
SilenceErrors: true,
|
|
||||||
SilenceUsage: true,
|
|
||||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
opt.Initialize()
|
opt.Initialize()
|
||||||
return config.Load(opt.ConfigFile)
|
return config.Load(opt.ConfigFile)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2022 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2022 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -42,8 +43,6 @@ func NewIndexListCmd(_ context.Context, opt *options.Common) *cobra.Command {
|
||||||
Long: "List all the added indexes that were configured in falcoctl",
|
Long: "List all the added indexes that were configured in falcoctl",
|
||||||
Args: cobra.ExactArgs(0),
|
Args: cobra.ExactArgs(0),
|
||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
SilenceErrors: true,
|
|
||||||
SilenceUsage: true,
|
|
||||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||||
return o.RunIndexList()
|
return o.RunIndexList()
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2022 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2022 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -42,8 +43,6 @@ func NewIndexRemoveCmd(ctx context.Context, opt *options.Common) *cobra.Command
|
||||||
Long: "Remove an index from the local falcoctl configuration",
|
Long: "Remove an index from the local falcoctl configuration",
|
||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.MinimumNArgs(1),
|
||||||
Aliases: []string{"rm"},
|
Aliases: []string{"rm"},
|
||||||
SilenceErrors: true,
|
|
||||||
SilenceUsage: true,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return o.RunIndexRemove(ctx, args)
|
return o.RunIndexRemove(ctx, args)
|
||||||
},
|
},
|
||||||
|
@ -53,30 +52,32 @@ func NewIndexRemoveCmd(ctx context.Context, opt *options.Common) *cobra.Command
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *indexRemoveOptions) RunIndexRemove(ctx context.Context, args []string) error {
|
func (o *indexRemoveOptions) RunIndexRemove(ctx context.Context, args []string) error {
|
||||||
o.Printer.Verbosef("Creating in-memory cache using indexes file %q and indexes directory %q", config.IndexesFile, config.IndexesDir)
|
logger := o.Printer.Logger
|
||||||
|
|
||||||
|
logger.Debug("Creating in-memory cache using", logger.Args("indexes file", config.IndexesFile, "indexes directory", config.IndexesDir))
|
||||||
indexCache, err := cache.New(ctx, config.IndexesFile, config.IndexesDir)
|
indexCache, err := cache.New(ctx, config.IndexesFile, config.IndexesDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to create index cache: %w", err)
|
return fmt.Errorf("unable to create index cache: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, name := range args {
|
for _, name := range args {
|
||||||
o.Printer.Info.Printfln("Removing index %q", name)
|
logger.Info("Removing index", logger.Args("name", name))
|
||||||
if err = indexCache.Remove(name); err != nil {
|
if err = indexCache.Remove(name); err != nil {
|
||||||
return fmt.Errorf("unable to remove index: %w", err)
|
return fmt.Errorf("unable to remove index: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
o.Printer.Verbosef("Writing cache to disk")
|
logger.Debug("Writing cache to disk")
|
||||||
if _, err = indexCache.Write(); err != nil {
|
if _, err = indexCache.Write(); err != nil {
|
||||||
return fmt.Errorf("unable to write cache to disk: %w", err)
|
return fmt.Errorf("unable to write cache to disk: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
o.Printer.Verbosef("Removing indexes entries from configuration file %q", o.ConfigFile)
|
logger.Debug("Removing indexes entries from configuration", logger.Args("file", o.ConfigFile))
|
||||||
if err = config.RemoveIndexes(args, o.ConfigFile); err != nil {
|
if err = config.RemoveIndexes(args, o.ConfigFile); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
o.Printer.Success.Printfln("Indexes successfully removed")
|
logger.Info("Indexes successfully removed")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2022 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2022 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -41,7 +42,6 @@ func NewIndexUpdateCmd(ctx context.Context, opt *options.Common) *cobra.Command
|
||||||
Short: "Update an existing index",
|
Short: "Update an existing index",
|
||||||
Long: "Update an existing index",
|
Long: "Update an existing index",
|
||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.MinimumNArgs(1),
|
||||||
SilenceErrors: true,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return o.RunIndexUpdate(ctx, args)
|
return o.RunIndexUpdate(ctx, args)
|
||||||
},
|
},
|
||||||
|
@ -51,25 +51,27 @@ func NewIndexUpdateCmd(ctx context.Context, opt *options.Common) *cobra.Command
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *indexUpdateOptions) RunIndexUpdate(ctx context.Context, args []string) error {
|
func (o *indexUpdateOptions) RunIndexUpdate(ctx context.Context, args []string) error {
|
||||||
o.Printer.Verbosef("Creating in-memory cache using indexes file %q and indexes directory %q", config.IndexesFile, config.IndexesDir)
|
logger := o.Printer.Logger
|
||||||
|
|
||||||
|
logger.Debug("Creating in-memory cache using", logger.Args("indexes file", config.IndexesFile, "indexes directory", config.IndexesDir))
|
||||||
indexCache, err := cache.New(ctx, config.IndexesFile, config.IndexesDir)
|
indexCache, err := cache.New(ctx, config.IndexesFile, config.IndexesDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to create index cache: %w", err)
|
return fmt.Errorf("unable to create index cache: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
o.Printer.Info.Printfln("Updating index %q", arg)
|
logger.Info("Updating index file", logger.Args("name", arg))
|
||||||
if err := indexCache.Update(ctx, arg); err != nil {
|
if err := indexCache.Update(ctx, arg); err != nil {
|
||||||
return fmt.Errorf("an error occurred while updating index %q: %w", arg, err)
|
return fmt.Errorf("an error occurred while updating index %q: %w", arg, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
o.Printer.Verbosef("Writing cache to disk")
|
logger.Debug("Writing cache to disk")
|
||||||
if _, err = indexCache.Write(); err != nil {
|
if _, err = indexCache.Write(); err != nil {
|
||||||
return fmt.Errorf("unable to write cache to disk: %w", err)
|
return fmt.Errorf("unable to write cache to disk: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
o.Printer.Success.Printfln("Indexes successfully updated")
|
logger.Info("Indexes successfully updated")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2023 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -32,7 +33,6 @@ func NewAuthCmd(ctx context.Context, opt *commonoptions.Common) *cobra.Command {
|
||||||
DisableFlagsInUseLine: true,
|
DisableFlagsInUseLine: true,
|
||||||
Short: "Handle authentication towards OCI registries",
|
Short: "Handle authentication towards OCI registries",
|
||||||
Long: "Handle authentication towards OCI registries",
|
Long: "Handle authentication towards OCI registries",
|
||||||
SilenceErrors: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.AddCommand(basic.NewBasicCmd(ctx, opt))
|
cmd.AddCommand(basic.NewBasicCmd(ctx, opt))
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2022 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -15,21 +16,31 @@
|
||||||
package basic
|
package basic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
credentials "github.com/oras-project/oras-credentials-go"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"golang.org/x/term"
|
||||||
|
"oras.land/oras-go/v2/registry/remote/credentials"
|
||||||
|
|
||||||
"github.com/falcosecurity/falcoctl/internal/config"
|
"github.com/falcosecurity/falcoctl/internal/config"
|
||||||
"github.com/falcosecurity/falcoctl/internal/login/basic"
|
"github.com/falcosecurity/falcoctl/internal/login/basic"
|
||||||
"github.com/falcosecurity/falcoctl/internal/utils"
|
"github.com/falcosecurity/falcoctl/internal/utils"
|
||||||
"github.com/falcosecurity/falcoctl/pkg/oci/authn"
|
"github.com/falcosecurity/falcoctl/pkg/oci/authn"
|
||||||
"github.com/falcosecurity/falcoctl/pkg/options"
|
"github.com/falcosecurity/falcoctl/pkg/options"
|
||||||
|
"github.com/falcosecurity/falcoctl/pkg/output"
|
||||||
)
|
)
|
||||||
|
|
||||||
type loginOptions struct {
|
type loginOptions struct {
|
||||||
*options.Common
|
*options.Common
|
||||||
|
username string
|
||||||
|
password string
|
||||||
|
passwordFromStdin bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBasicCmd returns the basic command.
|
// NewBasicCmd returns the basic command.
|
||||||
|
@ -42,24 +53,56 @@ func NewBasicCmd(ctx context.Context, opt *options.Common) *cobra.Command {
|
||||||
Use: "basic [hostname]",
|
Use: "basic [hostname]",
|
||||||
DisableFlagsInUseLine: true,
|
DisableFlagsInUseLine: true,
|
||||||
Short: "Login to an OCI registry",
|
Short: "Login to an OCI registry",
|
||||||
Long: "Login to an OCI registry to push and pull artifacts",
|
Long: `Login to an OCI registry
|
||||||
|
|
||||||
|
Example - Log in with username and password from command line flags:
|
||||||
|
falcoctl registry auth basic -u username -p password localhost:5000
|
||||||
|
|
||||||
|
Example - Login with username and password from env variables:
|
||||||
|
FALCOCTL_REGISTRY_AUTH_BASIC_USERNAME=username FALCOCTL_REGISTRY_AUTH_BASIC_PASSWORD=password falcoctl registry auth basic localhost:5000
|
||||||
|
|
||||||
|
Example - Login with username and password from stdin:
|
||||||
|
falcoctl registry auth basic -u username --password-stdin localhost:5000
|
||||||
|
|
||||||
|
Example - Login with username and password in an interactive prompt:
|
||||||
|
falcoctl registry auth basic localhost:5000
|
||||||
|
`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
SilenceErrors: true,
|
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
SilenceUsage: true,
|
_ = viper.BindPFlag("registry.auth.basic.username", cmd.Flags().Lookup("username"))
|
||||||
|
_ = viper.BindPFlag("registry.auth.basic.password", cmd.Flags().Lookup("password"))
|
||||||
|
_ = viper.BindPFlag("registry.auth.basic.password_stdin", cmd.Flags().Lookup("password-stdin"))
|
||||||
|
|
||||||
|
o.username = viper.GetString("registry.auth.basic.username")
|
||||||
|
o.password = viper.GetString("registry.auth.basic.password")
|
||||||
|
o.passwordFromStdin = viper.GetBool("registry.auth.basic.password_stdin")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return o.RunBasic(ctx, args)
|
return o.RunBasic(ctx, args)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd.Flags().StringVarP(&o.username, "username", "u", "", "registry username")
|
||||||
|
cmd.Flags().StringVarP(&o.password, "password", "p", "", "registry password")
|
||||||
|
cmd.Flags().BoolVar(&o.passwordFromStdin, "password-stdin", false, "read password from stdin")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunBasic executes the business logic for the basic command.
|
// RunBasic executes the business logic for the basic command.
|
||||||
func (o *loginOptions) RunBasic(ctx context.Context, args []string) error {
|
func (o *loginOptions) RunBasic(ctx context.Context, args []string) error {
|
||||||
reg := args[0]
|
var reg string
|
||||||
|
logger := o.Printer.Logger
|
||||||
|
|
||||||
user, token, err := utils.GetCredentials(o.Printer)
|
// Allow to have the registry expressed as a ref, but actually extract it.
|
||||||
|
reg, err := utils.GetRegistryFromRef(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
reg = args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := getCredentials(o.Printer, o); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,11 +117,46 @@ func (o *loginOptions) RunBasic(ctx context.Context, args []string) error {
|
||||||
return fmt.Errorf("unable to create new store: %w", err)
|
return fmt.Errorf("unable to create new store: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := basic.Login(ctx, client, credentialStore, reg, user, token); err != nil {
|
if err := basic.Login(ctx, client, credentialStore, reg, o.username, o.password); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
o.Printer.Verbosef("credentials added to credential store")
|
logger.Debug("Credentials added", logger.Args("credential store", config.RegistryCredentialConfPath()))
|
||||||
o.Printer.Success.Println("Login succeeded")
|
logger.Info("Login succeeded", logger.Args("registry", reg, "user", o.username))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCredentials is used to retrieve username and password from standard input.
|
||||||
|
func getCredentials(p *output.Printer, opt *loginOptions) error {
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
|
||||||
|
if opt.username == "" {
|
||||||
|
p.DefaultText.Print(p.FormatTitleAsLoggerInfo("Enter username:"))
|
||||||
|
username, err := reader.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
opt.username = strings.TrimSpace(username)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt.password == "" {
|
||||||
|
if opt.passwordFromStdin {
|
||||||
|
password, err := io.ReadAll(os.Stdin)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
opt.password = strings.TrimSuffix(string(password), "\n")
|
||||||
|
opt.password = strings.TrimSuffix(opt.password, "\r")
|
||||||
|
} else {
|
||||||
|
p.DefaultText.Print(p.FormatTitleAsLoggerInfo("Enter password: "))
|
||||||
|
bytePassword, err := term.ReadPassword(int(os.Stdin.Fd()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
opt.password = string(bytePassword)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2023 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -101,7 +102,6 @@ var _ = BeforeSuite(func() {
|
||||||
// Create and configure the common options.
|
// Create and configure the common options.
|
||||||
opt = commonoptions.NewOptions()
|
opt = commonoptions.NewOptions()
|
||||||
opt.Initialize(commonoptions.WithWriter(output))
|
opt.Initialize(commonoptions.WithWriter(output))
|
||||||
opt.Printer.DisableStylingf()
|
|
||||||
|
|
||||||
// Start the local registry.
|
// Start the local registry.
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -109,12 +109,28 @@ var _ = BeforeSuite(func() {
|
||||||
Expect(err).ToNot(BeNil())
|
Expect(err).ToNot(BeNil())
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// Check that the registry is up and accepting connections.
|
||||||
|
Eventually(func(g Gomega) error {
|
||||||
|
res, err := http.Get(fmt.Sprintf("http://%s", config.HTTP.Addr))
|
||||||
|
g.Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
g.Expect(res.StatusCode).Should(Equal(http.StatusOK))
|
||||||
|
return err
|
||||||
|
}).WithTimeout(time.Second * 5).ShouldNot(HaveOccurred())
|
||||||
|
|
||||||
// Start the local registry with basic authentication.
|
// Start the local registry with basic authentication.
|
||||||
go func() {
|
go func() {
|
||||||
err := testutils.StartRegistry(context.Background(), configBasic)
|
err := testutils.StartRegistry(context.Background(), configBasic)
|
||||||
Expect(err).ToNot(BeNil())
|
Expect(err).ToNot(BeNil())
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// Check that the registry is up and accepting connections.
|
||||||
|
Eventually(func(g Gomega) error {
|
||||||
|
res, err := http.Get(fmt.Sprintf("https://%s", configBasic.HTTP.Addr))
|
||||||
|
g.Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
g.Expect(res.StatusCode).Should(Equal(http.StatusOK))
|
||||||
|
return err
|
||||||
|
}).WithTimeout(time.Second * 5).ShouldNot(HaveOccurred())
|
||||||
|
|
||||||
// Create temporary directory used to save the configuration file.
|
// Create temporary directory used to save the configuration file.
|
||||||
configFile, err = testutils.CreateEmptyFile("falcoctl.yaml")
|
configFile, err = testutils.CreateEmptyFile("falcoctl.yaml")
|
||||||
Expect(err).Should(Succeed())
|
Expect(err).Should(Succeed())
|
||||||
|
@ -130,5 +146,5 @@ var _ = AfterSuite(func() {
|
||||||
func executeRoot(args []string) error {
|
func executeRoot(args []string) error {
|
||||||
rootCmd.SetArgs(args)
|
rootCmd.SetArgs(args)
|
||||||
rootCmd.SetOut(output)
|
rootCmd.SetOut(output)
|
||||||
return cmd.Execute(rootCmd, opt.Printer)
|
return cmd.Execute(rootCmd, opt)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2023 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -58,7 +59,34 @@ Global Flags:
|
||||||
`
|
`
|
||||||
|
|
||||||
//nolint:unused // false positive
|
//nolint:unused // false positive
|
||||||
var registryAuthBasicHelp = `Login to an OCI registry to push and pull artifacts`
|
var registryAuthBasicHelp = `Login to an OCI registry
|
||||||
|
|
||||||
|
Example - Log in with username and password from command line flags:
|
||||||
|
falcoctl registry auth basic -u username -p password localhost:5000
|
||||||
|
|
||||||
|
Example - Login with username and password from env variables:
|
||||||
|
FALCOCTL_REGISTRY_AUTH_BASIC_USERNAME=username FALCOCTL_REGISTRY_AUTH_BASIC_PASSWORD=password falcoctl registry auth basic localhost:5000
|
||||||
|
|
||||||
|
Example - Login with username and password from stdin:
|
||||||
|
falcoctl registry auth basic -u username --password-stdin localhost:5000
|
||||||
|
|
||||||
|
Example - Login with username and password in an interactive prompt:
|
||||||
|
falcoctl registry auth basic localhost:5000
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
falcoctl registry auth basic [hostname]
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-h, --help help for basic
|
||||||
|
-p, --password string registry password
|
||||||
|
--password-stdin read password from stdin
|
||||||
|
-u, --username string registry username
|
||||||
|
|
||||||
|
Global Flags:
|
||||||
|
--config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml")
|
||||||
|
--log-format string Set formatting for logs (color, text, json) (default "color")
|
||||||
|
--log-level string Set level for logs (info, warn, debug, trace) (default "info")
|
||||||
|
`
|
||||||
|
|
||||||
//nolint:unused // false positive
|
//nolint:unused // false positive
|
||||||
var registryAuthBasicAssertFailedBehavior = func(usage, specificError string) {
|
var registryAuthBasicAssertFailedBehavior = func(usage, specificError string) {
|
||||||
|
@ -106,32 +134,8 @@ var registryAuthBasicTests = Describe("auth", func() {
|
||||||
args = []string{registryCmd, authCmd, basicCmd}
|
args = []string{registryCmd, authCmd, basicCmd}
|
||||||
})
|
})
|
||||||
registryAuthBasicAssertFailedBehavior(registryAuthBasicUsage,
|
registryAuthBasicAssertFailedBehavior(registryAuthBasicUsage,
|
||||||
"ERRO: accepts 1 arg(s), received 0")
|
"ERROR accepts 1 arg(s), received 0")
|
||||||
})
|
})
|
||||||
|
|
||||||
/*
|
|
||||||
When("wrong credentials", func() {
|
|
||||||
BeforeEach(func() {
|
|
||||||
|
|
||||||
ptyFile, ttyFile, err := pty.Open()
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
|
|
||||||
os.Stdin = ttyFile
|
|
||||||
input := `username1
|
|
||||||
password1
|
|
||||||
`
|
|
||||||
_, err = ptyFile.Write([]byte(input))
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
|
|
||||||
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
|
||||||
|
|
||||||
args = []string{registryCmd, authCmd, basicCmd, "--config", configFile, registryBasic}
|
|
||||||
})
|
|
||||||
|
|
||||||
registryAuthBasicAssertFailedBehavior(registryAuthBasicUsage,
|
|
||||||
"ERRO: accepts 0 arg(s), received 0")
|
|
||||||
})
|
|
||||||
*/
|
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2022 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2023 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2023 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2023 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -52,8 +53,6 @@ func NewGcpCmd(ctx context.Context, opt *options.Common) *cobra.Command {
|
||||||
Short: "Register an Artifact Registry to log in using GCP Application Default credentials",
|
Short: "Register an Artifact Registry to log in using GCP Application Default credentials",
|
||||||
Long: longGcp,
|
Long: longGcp,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
SilenceErrors: true,
|
|
||||||
SilenceUsage: true,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return o.RunGcp(ctx, args)
|
return o.RunGcp(ctx, args)
|
||||||
},
|
},
|
||||||
|
@ -65,20 +64,21 @@ func NewGcpCmd(ctx context.Context, opt *options.Common) *cobra.Command {
|
||||||
// RunGcp executes the business logic for the gcp command.
|
// RunGcp executes the business logic for the gcp command.
|
||||||
func (o *RegistryGcpOptions) RunGcp(ctx context.Context, args []string) error {
|
func (o *RegistryGcpOptions) RunGcp(ctx context.Context, args []string) error {
|
||||||
var err error
|
var err error
|
||||||
|
logger := o.Printer.Logger
|
||||||
reg := args[0]
|
reg := args[0]
|
||||||
if err = gcp.Login(ctx, reg); err != nil {
|
if err = gcp.Login(ctx, reg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
o.Printer.Success.Printfln("GCP authentication successful for %q", reg)
|
logger.Info("GCP authentication successful", logger.Args("registry", reg))
|
||||||
|
|
||||||
o.Printer.Verbosef("Adding new gcp entry to configuration file %q", o.ConfigFile)
|
logger.Debug("Adding new gcp entry to configuration", logger.Args("file", o.ConfigFile))
|
||||||
if err = config.AddGcp([]config.GcpAuth{{
|
if err = config.AddGcp([]config.GcpAuth{{
|
||||||
Registry: reg,
|
Registry: reg,
|
||||||
}}, o.ConfigFile); err != nil {
|
}}, o.ConfigFile); err != nil {
|
||||||
return fmt.Errorf("index entry %q: %w", reg, err)
|
return fmt.Errorf("index entry %q: %w", reg, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
o.Printer.Success.Printfln("GCP authentication entry for %q successfully added in configuration file", reg)
|
logger.Info("GCG authentication entry successfully added", logger.Args("registry", reg, "confgi file", o.ConfigFile))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2022 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2022 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -16,6 +17,7 @@ package oauth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/oauth2/clientcredentials"
|
"golang.org/x/oauth2/clientcredentials"
|
||||||
|
@ -23,6 +25,7 @@ import (
|
||||||
"github.com/falcosecurity/falcoctl/internal/config"
|
"github.com/falcosecurity/falcoctl/internal/config"
|
||||||
"github.com/falcosecurity/falcoctl/internal/login/oauth"
|
"github.com/falcosecurity/falcoctl/internal/login/oauth"
|
||||||
"github.com/falcosecurity/falcoctl/pkg/options"
|
"github.com/falcosecurity/falcoctl/pkg/options"
|
||||||
|
"github.com/falcosecurity/falcoctl/pkg/output"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -57,8 +60,6 @@ func NewOauthCmd(ctx context.Context, opt *options.Common) *cobra.Command {
|
||||||
Short: "Retrieve access and refresh tokens for OAuth2.0 client credentials flow authentication",
|
Short: "Retrieve access and refresh tokens for OAuth2.0 client credentials flow authentication",
|
||||||
Long: longOauth,
|
Long: longOauth,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
SilenceErrors: true,
|
|
||||||
SilenceUsage: true,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return o.RunOAuth(ctx, args)
|
return o.RunOAuth(ctx, args)
|
||||||
},
|
},
|
||||||
|
@ -66,17 +67,15 @@ func NewOauthCmd(ctx context.Context, opt *options.Common) *cobra.Command {
|
||||||
|
|
||||||
cmd.Flags().StringVar(&o.Conf.TokenURL, "token-url", "", "token URL used to get access and refresh tokens")
|
cmd.Flags().StringVar(&o.Conf.TokenURL, "token-url", "", "token URL used to get access and refresh tokens")
|
||||||
if err := cmd.MarkFlagRequired("token-url"); err != nil {
|
if err := cmd.MarkFlagRequired("token-url"); err != nil {
|
||||||
o.Printer.Error.Println("unable to mark flag \"token-url\" as required")
|
output.ExitOnErr(o.Printer, fmt.Errorf("unable to mark flag \"token-url\" as required"))
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
cmd.Flags().StringVar(&o.Conf.ClientID, "client-id", "", "client ID of the OAuth2.0 app")
|
cmd.Flags().StringVar(&o.Conf.ClientID, "client-id", "", "client ID of the OAuth2.0 app")
|
||||||
if err := cmd.MarkFlagRequired("client-id"); err != nil {
|
if err := cmd.MarkFlagRequired("client-id"); err != nil {
|
||||||
o.Printer.Error.Println("unable to mark flag \"client-id\" as required")
|
output.ExitOnErr(o.Printer, fmt.Errorf("unable to mark flag \"client-id\" as required"))
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
cmd.Flags().StringVar(&o.Conf.ClientSecret, "client-secret", "", "client secret of the OAuth2.0 app")
|
cmd.Flags().StringVar(&o.Conf.ClientSecret, "client-secret", "", "client secret of the OAuth2.0 app")
|
||||||
if err := cmd.MarkFlagRequired("client-secret"); err != nil {
|
if err := cmd.MarkFlagRequired("client-secret"); err != nil {
|
||||||
o.Printer.Error.Println("unable to mark flag \"client-secret\" as required")
|
output.ExitOnErr(o.Printer, fmt.Errorf("unable to mark flag \"client-secret\" as required"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
cmd.Flags().StringSliceVar(&o.Conf.Scopes, "scopes", nil, "comma separeted list of scopes for which requesting access")
|
cmd.Flags().StringSliceVar(&o.Conf.Scopes, "scopes", nil, "comma separeted list of scopes for which requesting access")
|
||||||
|
@ -90,6 +89,6 @@ func (o *RegistryOauthOptions) RunOAuth(ctx context.Context, args []string) erro
|
||||||
if err := oauth.Login(ctx, reg, &o.Conf); err != nil {
|
if err := oauth.Login(ctx, reg, &o.Conf); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
o.Printer.Success.Printfln("client credentials correctly saved in %q", config.ClientCredentialsFile)
|
o.Printer.Logger.Info("Client credentials correctly saved", o.Printer.Logger.Args("file", config.ClientCredentialsFile))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2023 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -17,10 +18,12 @@ package oauth_test
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/distribution/distribution/v3/configuration"
|
"github.com/distribution/distribution/v3/configuration"
|
||||||
_ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
|
_ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
|
||||||
|
@ -83,7 +86,6 @@ var _ = BeforeSuite(func() {
|
||||||
// Create and configure the common options.
|
// Create and configure the common options.
|
||||||
opt = commonoptions.NewOptions()
|
opt = commonoptions.NewOptions()
|
||||||
opt.Initialize(commonoptions.WithWriter(output))
|
opt.Initialize(commonoptions.WithWriter(output))
|
||||||
opt.Printer.DisableStylingf()
|
|
||||||
|
|
||||||
// Create the oras registry.
|
// Create the oras registry.
|
||||||
orasRegistry, err = testutils.NewOrasRegistry(registry, true)
|
orasRegistry, err = testutils.NewOrasRegistry(registry, true)
|
||||||
|
@ -95,6 +97,14 @@ var _ = BeforeSuite(func() {
|
||||||
Expect(err).ToNot(BeNil())
|
Expect(err).ToNot(BeNil())
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// Check that the registry is up and accepting connections.
|
||||||
|
Eventually(func(g Gomega) error {
|
||||||
|
res, err := http.Get(fmt.Sprintf("http://%s", config.HTTP.Addr))
|
||||||
|
g.Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
g.Expect(res.StatusCode).Should(Equal(http.StatusOK))
|
||||||
|
return err
|
||||||
|
}).WithTimeout(time.Second * 5).ShouldNot(HaveOccurred())
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
err := testutils.StartOAuthServer(context.Background(), oauthPort)
|
err := testutils.StartOAuthServer(context.Background(), oauthPort)
|
||||||
Expect(err).ToNot(BeNil())
|
Expect(err).ToNot(BeNil())
|
||||||
|
@ -114,5 +124,5 @@ var _ = AfterSuite(func() {
|
||||||
func executeRoot(args []string) error {
|
func executeRoot(args []string) error {
|
||||||
rootCmd.SetArgs(args)
|
rootCmd.SetArgs(args)
|
||||||
rootCmd.SetOut(output)
|
rootCmd.SetOut(output)
|
||||||
return cmd.Execute(rootCmd, opt.Printer)
|
return cmd.Execute(rootCmd, opt)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2023 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -64,8 +65,9 @@ Flags:
|
||||||
|
|
||||||
Global Flags:
|
Global Flags:
|
||||||
--config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml")
|
--config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml")
|
||||||
--disable-styling Disable output styling such as spinners, progress bars and colors. Styling is automatically disabled if not attacched to a tty (default false)
|
--log-format string Set formatting for logs (color, text, json) (default "color")
|
||||||
-v, --verbose Enable verbose logs (default false)
|
--log-level string Set level for logs (info, warn, debug, trace) (default "info")
|
||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
//nolint:unused // false positive
|
//nolint:unused // false positive
|
||||||
|
@ -131,7 +133,7 @@ var registryAuthOAuthTests = Describe("auth", func() {
|
||||||
args = []string{registryCmd, authCmd, oauthCmd}
|
args = []string{registryCmd, authCmd, oauthCmd}
|
||||||
})
|
})
|
||||||
registryAuthOAuthAssertFailedBehavior(registryAuthOAuthUsage,
|
registryAuthOAuthAssertFailedBehavior(registryAuthOAuthUsage,
|
||||||
"ERRO: accepts 1 arg(s), received 0")
|
"ERROR accepts 1 arg(s), received 0")
|
||||||
})
|
})
|
||||||
|
|
||||||
When("wrong client id", func() {
|
When("wrong client id", func() {
|
||||||
|
@ -151,7 +153,7 @@ var registryAuthOAuthTests = Describe("auth", func() {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
registryAuthOAuthAssertFailedBehavior(registryAuthOAuthUsage,
|
registryAuthOAuthAssertFailedBehavior(registryAuthOAuthUsage,
|
||||||
`ERRO: wrong client credentials, unable to retrieve token`)
|
`ERROR wrong client credentials, unable to retrieve token`)
|
||||||
})
|
})
|
||||||
|
|
||||||
When("wrong client secret", func() {
|
When("wrong client secret", func() {
|
||||||
|
@ -171,7 +173,7 @@ var registryAuthOAuthTests = Describe("auth", func() {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
registryAuthOAuthAssertFailedBehavior(registryAuthOAuthUsage,
|
registryAuthOAuthAssertFailedBehavior(registryAuthOAuthUsage,
|
||||||
`ERRO: wrong client credentials, unable to retrieve token`)
|
`ERROR wrong client credentials, unable to retrieve token`)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -198,7 +200,7 @@ var registryAuthOAuthTests = Describe("auth", func() {
|
||||||
|
|
||||||
It("should successed", func() {
|
It("should successed", func() {
|
||||||
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(
|
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(
|
||||||
`INFO: client credentials correctly saved in`)))
|
`INFO Client credentials correctly saved`)))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2023 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2022 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2023 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -74,8 +75,6 @@ func NewPullCmd(ctx context.Context, opt *options.Common) *cobra.Command {
|
||||||
Short: "Pull a Falco OCI artifact from remote registry",
|
Short: "Pull a Falco OCI artifact from remote registry",
|
||||||
Long: longPull,
|
Long: longPull,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
SilenceErrors: true,
|
|
||||||
SilenceUsage: true,
|
|
||||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if err := o.Validate(); err != nil {
|
if err := o.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -87,6 +86,7 @@ func NewPullCmd(ctx context.Context, opt *options.Common) *cobra.Command {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
o.Common.Initialize()
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
@ -102,6 +102,7 @@ func NewPullCmd(ctx context.Context, opt *options.Common) *cobra.Command {
|
||||||
|
|
||||||
// RunPull executes the business logic for the pull command.
|
// RunPull executes the business logic for the pull command.
|
||||||
func (o *pullOptions) RunPull(ctx context.Context, args []string) error {
|
func (o *pullOptions) RunPull(ctx context.Context, args []string) error {
|
||||||
|
logger := o.Printer.Logger
|
||||||
ref := args[0]
|
ref := args[0]
|
||||||
|
|
||||||
registry, err := utils.GetRegistryFromRef(ref)
|
registry, err := utils.GetRegistryFromRef(ref)
|
||||||
|
@ -119,12 +120,12 @@ func (o *pullOptions) RunPull(ctx context.Context, args []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
o.Printer.Info.Printfln("Preparing to pull artifact %q", args[0])
|
logger.Info("Preparing to pull artifact", logger.Args("name", args[0]))
|
||||||
|
|
||||||
if o.destDir == "" {
|
if o.destDir == "" {
|
||||||
o.Printer.Info.Printfln("Pulling artifact in the current directory")
|
logger.Info("Pulling artifact in the current directory")
|
||||||
} else {
|
} else {
|
||||||
o.Printer.Info.Printfln("Pulling artifact in %q directory", o.destDir)
|
logger.Info("Pulling artifact in", logger.Args("directory", o.destDir))
|
||||||
}
|
}
|
||||||
|
|
||||||
os, arch := runtime.GOOS, runtime.GOARCH
|
os, arch := runtime.GOOS, runtime.GOARCH
|
||||||
|
@ -137,7 +138,7 @@ func (o *pullOptions) RunPull(ctx context.Context, args []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
o.Printer.Success.Printfln("Artifact of type %q pulled. Digest: %q", res.Type, res.Digest)
|
logger.Info("Artifact pulled", logger.Args("name", args[0], "type", res.Type, "digest", res.Digest))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2023 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -17,9 +18,11 @@ package pull_test
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/distribution/distribution/v3/configuration"
|
"github.com/distribution/distribution/v3/configuration"
|
||||||
_ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
|
_ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
|
||||||
|
@ -69,7 +72,6 @@ var _ = BeforeSuite(func() {
|
||||||
// Create and configure the common options.
|
// Create and configure the common options.
|
||||||
opt = commonoptions.NewOptions()
|
opt = commonoptions.NewOptions()
|
||||||
opt.Initialize(commonoptions.WithWriter(output))
|
opt.Initialize(commonoptions.WithWriter(output))
|
||||||
opt.Printer.DisableStylingf()
|
|
||||||
|
|
||||||
// Create the oras registry.
|
// Create the oras registry.
|
||||||
orasRegistry, err = testutils.NewOrasRegistry(registry, true)
|
orasRegistry, err = testutils.NewOrasRegistry(registry, true)
|
||||||
|
@ -81,6 +83,14 @@ var _ = BeforeSuite(func() {
|
||||||
Expect(err).ToNot(BeNil())
|
Expect(err).ToNot(BeNil())
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// Check that the registry is up and accepting connections.
|
||||||
|
Eventually(func(g Gomega) error {
|
||||||
|
res, err := http.Get(fmt.Sprintf("http://%s", config.HTTP.Addr))
|
||||||
|
g.Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
g.Expect(res.StatusCode).Should(Equal(http.StatusOK))
|
||||||
|
return err
|
||||||
|
}).WithTimeout(time.Second * 5).ShouldNot(HaveOccurred())
|
||||||
|
|
||||||
// Create temporary directory used to save the configuration file.
|
// Create temporary directory used to save the configuration file.
|
||||||
configFile, err = testutils.CreateEmptyFile("falcoctl.yaml")
|
configFile, err = testutils.CreateEmptyFile("falcoctl.yaml")
|
||||||
Expect(err).Should(Succeed())
|
Expect(err).Should(Succeed())
|
||||||
|
@ -96,5 +106,5 @@ var _ = AfterSuite(func() {
|
||||||
func executeRoot(args []string) error {
|
func executeRoot(args []string) error {
|
||||||
rootCmd.SetArgs(args)
|
rootCmd.SetArgs(args)
|
||||||
rootCmd.SetOut(output)
|
rootCmd.SetOut(output)
|
||||||
return cmd.Execute(rootCmd, opt.Printer)
|
return cmd.Execute(rootCmd, opt)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2023 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -143,7 +144,7 @@ var registryPullTests = Describe("pull", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
args = []string{registryCmd, pullCmd}
|
args = []string{registryCmd, pullCmd}
|
||||||
})
|
})
|
||||||
pullAssertFailedBehavior(registryPullUsage, "ERRO: accepts 1 arg(s), received 0")
|
pullAssertFailedBehavior(registryPullUsage, "ERROR accepts 1 arg(s), received 0")
|
||||||
})
|
})
|
||||||
|
|
||||||
When("unreachable registry", func() {
|
When("unreachable registry", func() {
|
||||||
|
@ -154,7 +155,7 @@ var registryPullTests = Describe("pull", func() {
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
args = []string{registryCmd, pullCmd, "noregistry/testrules", "--plain-http", "--config", configFile}
|
args = []string{registryCmd, pullCmd, "noregistry/testrules", "--plain-http", "--config", configFile}
|
||||||
})
|
})
|
||||||
pullAssertFailedBehavior(registryPullUsage, "ERRO: unable to connect to remote registry")
|
pullAssertFailedBehavior(registryPullUsage, "ERROR unable to connect to remote registry")
|
||||||
})
|
})
|
||||||
|
|
||||||
When("invalid repository", func() {
|
When("invalid repository", func() {
|
||||||
|
@ -166,7 +167,7 @@ var registryPullTests = Describe("pull", func() {
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
args = []string{registryCmd, pullCmd, newReg, "--plain-http", "--config", configFile}
|
args = []string{registryCmd, pullCmd, newReg, "--plain-http", "--config", configFile}
|
||||||
})
|
})
|
||||||
pullAssertFailedBehavior(registryPullUsage, fmt.Sprintf("ERRO: %s: not found", newReg))
|
pullAssertFailedBehavior(registryPullUsage, fmt.Sprintf("ERROR %s: not found", newReg))
|
||||||
})
|
})
|
||||||
|
|
||||||
When("unwritable --dest-dir", func() {
|
When("unwritable --dest-dir", func() {
|
||||||
|
@ -199,7 +200,7 @@ var registryPullTests = Describe("pull", func() {
|
||||||
artName := tmp[0]
|
artName := tmp[0]
|
||||||
tag := tmp[1]
|
tag := tmp[1]
|
||||||
expectedError := fmt.Sprintf(
|
expectedError := fmt.Sprintf(
|
||||||
"ERRO: unable to pull artifact generic-repo with %s tag from repo %s: failed to create file",
|
"ERROR unable to pull artifact generic-repo with %s tag from repo %s: failed to create file",
|
||||||
tag, artName)
|
tag, artName)
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(err).To(HaveOccurred())
|
||||||
Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(registryPullUsage)))
|
Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(registryPullUsage)))
|
||||||
|
@ -232,10 +233,8 @@ var registryPullTests = Describe("pull", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("check that fails and the usage is not printed", func() {
|
It("check that fails and the usage is not printed", func() {
|
||||||
expectedError := fmt.Sprintf(
|
expectedError := fmt.Sprintf("ERROR unable to pull artifact %s with tag %s from repo %s: failed to ensure directories of the target path: "+
|
||||||
"ERRO: unable to push artifact failed to ensure directories of the target path: mkdir %s: permission denied\n"+
|
"mkdir %s: permission denied", artifact, tag, artifact, destDir)
|
||||||
"ERRO: unable to pull artifact %s with tag %s from repo %s: failed to ensure directories of the target path: mkdir %s: permission denied",
|
|
||||||
destDir, artifact, tag, artifact, destDir)
|
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(err).To(HaveOccurred())
|
||||||
Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(registryPullUsage)))
|
Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(registryPullUsage)))
|
||||||
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(expectedError)))
|
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(expectedError)))
|
||||||
|
@ -263,7 +262,7 @@ var registryPullTests = Describe("pull", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("check that fails and the usage is not printed", func() {
|
It("check that fails and the usage is not printed", func() {
|
||||||
expectedError := fmt.Sprintf("ERRO: %s: not found", registry+repo+"@"+wrongDigest)
|
expectedError := fmt.Sprintf("ERROR %s: not found", registry+repo+"@"+wrongDigest)
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(err).To(HaveOccurred())
|
||||||
Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(registryPullUsage)))
|
Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(registryPullUsage)))
|
||||||
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(expectedError)))
|
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(expectedError)))
|
||||||
|
@ -281,7 +280,7 @@ var registryPullTests = Describe("pull", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("check that fails and the usage is not printed", func() {
|
It("check that fails and the usage is not printed", func() {
|
||||||
expectedError := fmt.Sprintf("ERRO: cannot extract registry name from ref %q", ref)
|
expectedError := fmt.Sprintf("ERROR cannot extract registry name from ref %q", ref)
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(err).To(HaveOccurred())
|
||||||
Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(registryPullUsage)))
|
Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(registryPullUsage)))
|
||||||
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(expectedError)))
|
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(expectedError)))
|
||||||
|
@ -297,8 +296,8 @@ var registryPullTests = Describe("pull", func() {
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
args = []string{registryCmd, pullCmd, newReg, "--plain-http", "--config", configFile}
|
args = []string{registryCmd, pullCmd, newReg, "--plain-http", "--config", configFile}
|
||||||
})
|
})
|
||||||
pullAssertFailedBehavior(registryPullUsage, fmt.Sprintf("ERRO: unable to create new repository with ref %s: "+
|
pullAssertFailedBehavior(registryPullUsage, fmt.Sprintf("ERROR unable to create new repository with ref %s: "+
|
||||||
"invalid reference: invalid digest; invalid checksum digest format\n", newReg))
|
"invalid reference: invalid digest %q: invalid checksum digest format\n", newReg, "something"))
|
||||||
})
|
})
|
||||||
|
|
||||||
When("invalid platform", func() {
|
When("invalid platform", func() {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2022 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2022 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -21,7 +22,10 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/blang/semver/v4"
|
||||||
|
"github.com/pterm/pterm"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
"github.com/falcosecurity/falcoctl/internal/utils"
|
"github.com/falcosecurity/falcoctl/internal/utils"
|
||||||
"github.com/falcosecurity/falcoctl/pkg/oci"
|
"github.com/falcosecurity/falcoctl/pkg/oci"
|
||||||
|
@ -48,6 +52,10 @@ Example - Push artifact "myplugin.tar.gz" of type "plugin" for multiple platform
|
||||||
Example - Push artifact "myrulesfile.tar.gz" of type "rulesfile":
|
Example - Push artifact "myrulesfile.tar.gz" of type "rulesfile":
|
||||||
falcoctl registry push --type rulesfile --version "0.1.2" localhost:5000/myrulesfile:latest myrulesfile.tar.gz
|
falcoctl registry push --type rulesfile --version "0.1.2" localhost:5000/myrulesfile:latest myrulesfile.tar.gz
|
||||||
|
|
||||||
|
Example - Push artifact "myrulesfile.tar.gz" of type "rulesfile" with floating tags for the major and minor versions (0 and 0.1):
|
||||||
|
falcoctl registry push --type rulesfile --version "0.1.2" localhost:5000/myrulesfile:latest myrulesfile.tar.gz \
|
||||||
|
--add-floating-tags
|
||||||
|
|
||||||
Example - Push artifact "myrulesfile.tar.gz" of type "rulesfile" to an insecure registry:
|
Example - Push artifact "myrulesfile.tar.gz" of type "rulesfile" to an insecure registry:
|
||||||
falcoctl registry push --type rulesfile --version "0.1.2" --plain-http localhost:5000/myrulesfile:latest myrulesfile.tar.gz
|
falcoctl registry push --type rulesfile --version "0.1.2" --plain-http localhost:5000/myrulesfile:latest myrulesfile.tar.gz
|
||||||
|
|
||||||
|
@ -72,7 +80,7 @@ type pushOptions struct {
|
||||||
*options.Registry
|
*options.Registry
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o pushOptions) validate() error {
|
func (o *pushOptions) validate() error {
|
||||||
return o.Artifact.Validate()
|
return o.Artifact.Validate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,8 +98,6 @@ func NewPushCmd(ctx context.Context, opt *options.Common) *cobra.Command {
|
||||||
Short: "Push a Falco OCI artifact to remote registry",
|
Short: "Push a Falco OCI artifact to remote registry",
|
||||||
Long: longPush,
|
Long: longPush,
|
||||||
Args: cobra.MinimumNArgs(2),
|
Args: cobra.MinimumNArgs(2),
|
||||||
SilenceErrors: true,
|
|
||||||
SilenceUsage: true,
|
|
||||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if err := o.validate(); err != nil {
|
if err := o.validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -121,8 +127,9 @@ func (o *pushOptions) runPush(ctx context.Context, args []string) error {
|
||||||
ref := args[0]
|
ref := args[0]
|
||||||
paths := args[1:]
|
paths := args[1:]
|
||||||
// When creating the tar.gz archives we need to remove them after we are done.
|
// When creating the tar.gz archives we need to remove them after we are done.
|
||||||
// We save the temporary dir where they live here.
|
// Holds the path for each temporary dir.
|
||||||
var toBeDeleted string
|
var toBeDeletedTmpDirs []string
|
||||||
|
logger := o.Printer.Logger
|
||||||
|
|
||||||
registry, err := utils.GetRegistryFromRef(ref)
|
registry, err := utils.GetRegistryFromRef(ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -139,37 +146,43 @@ func (o *pushOptions) runPush(ctx context.Context, args []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
o.Printer.Info.Printfln("Preparing to push artifact %q of type %q", args[0], o.ArtifactType)
|
logger.Info("Preparing to push artifact", o.Printer.Logger.Args("name", args[0], "type", o.ArtifactType))
|
||||||
|
|
||||||
// Make sure to remove temporary working dir.
|
// Make sure to remove temporary working dirs.
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := os.RemoveAll(toBeDeleted); err != nil {
|
for _, dir := range toBeDeletedTmpDirs {
|
||||||
o.Printer.Warning.Printfln("Unable to remove temporary dir %q: %s", toBeDeleted, err.Error())
|
logger.Debug("Removing temporary dir", logger.Args("name", dir))
|
||||||
|
if err := os.RemoveAll(dir); err != nil {
|
||||||
|
logger.Warn("Unable to remove temporary dir", logger.Args("name", dir, "error", err.Error()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
config := &oci.ArtifactConfig{
|
||||||
|
Name: o.Name,
|
||||||
|
Version: o.Version,
|
||||||
|
}
|
||||||
|
|
||||||
for i, p := range paths {
|
for i, p := range paths {
|
||||||
if err = utils.IsTarGz(filepath.Clean(p)); err != nil && !errors.Is(err, utils.ErrNotTarGz) {
|
if err = utils.IsTarGz(filepath.Clean(p)); err != nil && !errors.Is(err, utils.ErrNotTarGz) {
|
||||||
return err
|
return err
|
||||||
} else if err == nil {
|
} else if err == nil {
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
path, err := utils.CreateTarGzArchive(p)
|
if o.ArtifactType == oci.Rulesfile {
|
||||||
|
if config, err = rulesConfigLayer(o.Printer.Logger, p, o.Artifact); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
path, err := utils.CreateTarGzArchive("", p, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
paths[i] = path
|
paths[i] = path
|
||||||
if toBeDeleted == "" {
|
toBeDeletedTmpDirs = append(toBeDeletedTmpDirs, filepath.Dir(path))
|
||||||
toBeDeleted = filepath.Dir(path)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup OCI artifact configuration
|
|
||||||
config := oci.ArtifactConfig{
|
|
||||||
Name: o.Name,
|
|
||||||
Version: o.Version,
|
|
||||||
}
|
|
||||||
if config.Name == "" {
|
if config.Name == "" {
|
||||||
// extract artifact name from ref, if not provided by the user
|
// extract artifact name from ref, if not provided by the user
|
||||||
if config.Name, err = utils.NameFromRef(ref); err != nil {
|
if config.Name, err = utils.NameFromRef(ref); err != nil {
|
||||||
|
@ -183,10 +196,18 @@ func (o *pushOptions) runPush(ctx context.Context, args []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if o.AutoFloatingTags {
|
||||||
|
v, err := semver.Parse(o.Version)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("expected semver for the flag \"--version\": %w", err)
|
||||||
|
}
|
||||||
|
o.Tags = append(o.Tags, o.Version, fmt.Sprintf("%v", v.Major), fmt.Sprintf("%v.%v", v.Major, v.Minor))
|
||||||
|
}
|
||||||
|
|
||||||
opts := ocipusher.Options{
|
opts := ocipusher.Options{
|
||||||
ocipusher.WithTags(o.Tags...),
|
ocipusher.WithTags(o.Tags...),
|
||||||
ocipusher.WithAnnotationSource(o.AnnotationSource),
|
ocipusher.WithAnnotationSource(o.AnnotationSource),
|
||||||
ocipusher.WithArtifactConfig(config),
|
ocipusher.WithArtifactConfig(*config),
|
||||||
}
|
}
|
||||||
|
|
||||||
switch o.ArtifactType {
|
switch o.ArtifactType {
|
||||||
|
@ -194,6 +215,8 @@ func (o *pushOptions) runPush(ctx context.Context, args []string) error {
|
||||||
opts = append(opts, ocipusher.WithFilepathsAndPlatforms(paths, o.Platforms))
|
opts = append(opts, ocipusher.WithFilepathsAndPlatforms(paths, o.Platforms))
|
||||||
case oci.Rulesfile:
|
case oci.Rulesfile:
|
||||||
opts = append(opts, ocipusher.WithFilepaths(paths))
|
opts = append(opts, ocipusher.WithFilepaths(paths))
|
||||||
|
case oci.Asset:
|
||||||
|
opts = append(opts, ocipusher.WithFilepaths(paths))
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := pusher.Push(ctx, o.ArtifactType, ref, opts...)
|
res, err := pusher.Push(ctx, o.ArtifactType, ref, opts...)
|
||||||
|
@ -201,7 +224,120 @@ func (o *pushOptions) runPush(ctx context.Context, args []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
o.Printer.Success.Printfln("Artifact pushed. Digest: %q", res.Digest)
|
logger.Info("Artifact pushed", logger.Args("name", args[0], "type", res.Type, "digest", res.RootDigest))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// depsKey is the key for deps in the rulesfiles.
|
||||||
|
depsKey = "required_plugin_versions"
|
||||||
|
// engineKey is the key in the rulesfiles.
|
||||||
|
engineKey = "required_engine_version"
|
||||||
|
// engineRequirementKey is used as name for the engine requirement in the config layer for the rulesfile artifacts.
|
||||||
|
engineRequirementKey = "engine_version_semver"
|
||||||
|
)
|
||||||
|
|
||||||
|
func rulesConfigLayer(logger *pterm.Logger, filePath string, artifactOptions *options.Artifact) (*oci.ArtifactConfig, error) {
|
||||||
|
var data []map[string]interface{}
|
||||||
|
|
||||||
|
// Setup OCI artifact configuration
|
||||||
|
config := oci.ArtifactConfig{
|
||||||
|
Name: artifactOptions.Name,
|
||||||
|
Version: artifactOptions.Version,
|
||||||
|
}
|
||||||
|
|
||||||
|
yamlFile, err := os.ReadFile(filepath.Clean(filePath))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to open rulesfile %s: %w", filePath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := yaml.Unmarshal(yamlFile, &data); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to unmarshal rulesfile %s: %w", filePath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the artifact dependencies.
|
||||||
|
// Check if the user has provided any.
|
||||||
|
if len(artifactOptions.Dependencies) != 0 {
|
||||||
|
logger.Info("Dependencies provided by user", logger.Args("rulesfile", filePath))
|
||||||
|
if err = config.ParseDependencies(artifactOptions.Dependencies...); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If no user provided then try to parse them from the rulesfile.
|
||||||
|
var found bool
|
||||||
|
logger.Info("Parsing dependencies from: ", logger.Args("rulesfile", filePath))
|
||||||
|
var requiredPluginVersionsEntry interface{}
|
||||||
|
var ok bool
|
||||||
|
for _, entry := range data {
|
||||||
|
if requiredPluginVersionsEntry, ok = entry[depsKey]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var deps []oci.ArtifactDependency
|
||||||
|
byteData, err := yaml.Marshal(requiredPluginVersionsEntry)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to parse dependencies from rulesfile: %w", err)
|
||||||
|
}
|
||||||
|
err = yaml.Unmarshal(byteData, &deps)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to parse dependencies from rulesfile: %w", err)
|
||||||
|
}
|
||||||
|
logger.Info("Dependencies correctly parsed from rulesfile")
|
||||||
|
// Set the deps.
|
||||||
|
config.Dependencies = deps
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
logger.Warn("No dependencies were provided by the user and none were found in the rulesfile.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the requirements.
|
||||||
|
// Check if the user has provided any.
|
||||||
|
if len(artifactOptions.Requirements) != 0 {
|
||||||
|
logger.Info("Requirements provided by user")
|
||||||
|
if err = config.ParseRequirements(artifactOptions.Requirements...); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var found bool
|
||||||
|
var engineVersion string
|
||||||
|
logger.Info("Parsing requirements from: ", logger.Args("rulesfile", filePath))
|
||||||
|
// If no user provided requirements then try to parse them from the rulesfile.
|
||||||
|
for _, entry := range data {
|
||||||
|
if requiredEngineVersionEntry, ok := entry[engineKey]; ok {
|
||||||
|
// Check if the version is an int. This is for backward compatibility. The engine version used to be an
|
||||||
|
// int but internally used by falco as a semver minor version.
|
||||||
|
// 15 -> 0.15.0
|
||||||
|
if engVersionInt, ok := requiredEngineVersionEntry.(int); ok {
|
||||||
|
engineVersion = fmt.Sprintf("0.%d.0", engVersionInt)
|
||||||
|
} else {
|
||||||
|
engineVersion, ok = requiredEngineVersionEntry.(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("%s must be an int or a string respecting the semver specification, got type %T", engineKey, requiredEngineVersionEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it is in semver format.
|
||||||
|
if _, err := semver.Parse(engineVersion); err != nil {
|
||||||
|
return nil, fmt.Errorf("%s must be in semver format: %w", engineVersion, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the requirements.
|
||||||
|
config.Requirements = []oci.ArtifactRequirement{{
|
||||||
|
Name: engineRequirementKey,
|
||||||
|
Version: engineVersion,
|
||||||
|
}}
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
logger.Warn("No requirements were provided by the user and none were found in the rulesfile.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &config, nil
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,217 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2024 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
package push_test
|
||||||
|
|
||||||
|
// revive:disable
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/onsi/gomega/gbytes"
|
||||||
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
|
||||||
|
"github.com/falcosecurity/falcoctl/cmd"
|
||||||
|
"github.com/falcosecurity/falcoctl/internal/utils"
|
||||||
|
"github.com/falcosecurity/falcoctl/pkg/oci"
|
||||||
|
testutils "github.com/falcosecurity/falcoctl/pkg/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
// revive:enable
|
||||||
|
var _ = Describe("pushing plugins", func() {
|
||||||
|
var (
|
||||||
|
registryCmd = "registry"
|
||||||
|
pushCmd = "push"
|
||||||
|
version = "1.1.1"
|
||||||
|
// fullRepoName is set each time before each test.
|
||||||
|
fullRepoName string
|
||||||
|
// repoName same as fullRepoName.
|
||||||
|
repoName string
|
||||||
|
// It is set in the config layer.
|
||||||
|
artifactNameInConfigLayer = "test-push-plugins"
|
||||||
|
pushedTags = []string{"tag1", "tag2", "latest"}
|
||||||
|
|
||||||
|
// Plugin's platforms.
|
||||||
|
platformARM64 = "linux/arm64"
|
||||||
|
platformAMD64 = "linux/amd64"
|
||||||
|
|
||||||
|
// Paths pointing to plugins that will be pushed.
|
||||||
|
// Some of the functions expect these two variable to be set to valid paths.
|
||||||
|
// They are set in beforeEach blocks by tests that need them.
|
||||||
|
pluginOne string
|
||||||
|
pluginTwo string
|
||||||
|
// Data fetched from registry and used for assertions.
|
||||||
|
pluginData *testutils.PluginArtifact
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Used as flags for all the test cases.
|
||||||
|
requirement = "plugin_api_version:3.2.1"
|
||||||
|
anSource = "myrepo.com/rules.git"
|
||||||
|
pluginsRepoBaseName = "push-plugins-tests"
|
||||||
|
)
|
||||||
|
|
||||||
|
var AssertSuccessBehaviour = func(deps []oci.ArtifactDependency, reqs []oci.ArtifactRequirement, annotations map[string]string, platforms []string) {
|
||||||
|
It("should succeed", func() {
|
||||||
|
// We do not check the error here since we are checking it after
|
||||||
|
// pushing the artifact.
|
||||||
|
By("checking no error in output")
|
||||||
|
Expect(output).ShouldNot(gbytes.Say("ERROR"))
|
||||||
|
Expect(output).ShouldNot(gbytes.Say("Unable to remove temporary dir"))
|
||||||
|
|
||||||
|
By("checking descriptor")
|
||||||
|
Expect(pluginData.Descriptor.MediaType).Should(Equal(v1.MediaTypeImageIndex))
|
||||||
|
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(pluginData.Descriptor.Digest.String())))
|
||||||
|
|
||||||
|
By("checking index")
|
||||||
|
Expect(pluginData.Index.Manifests).Should(HaveLen(len(platforms)))
|
||||||
|
|
||||||
|
By("checking platforms")
|
||||||
|
for _, p := range platforms {
|
||||||
|
Expect(pluginData.Platforms).Should(HaveKey(p))
|
||||||
|
}
|
||||||
|
|
||||||
|
By("checking config layers")
|
||||||
|
for plat, p := range pluginData.Platforms {
|
||||||
|
By(fmt.Sprintf("platform %s", plat))
|
||||||
|
Expect(p.Config.Version).Should(Equal(version))
|
||||||
|
Expect(p.Config.Name).Should(Equal(artifactNameInConfigLayer))
|
||||||
|
|
||||||
|
By("checking dependencies")
|
||||||
|
Expect(p.Config.Dependencies).Should(HaveLen(len(deps)))
|
||||||
|
for _, dep := range deps {
|
||||||
|
Expect(p.Config.Dependencies).Should(ContainElement(dep))
|
||||||
|
}
|
||||||
|
|
||||||
|
By("checking requirements")
|
||||||
|
Expect(p.Config.Requirements).Should(HaveLen(len(reqs)))
|
||||||
|
for _, req := range reqs {
|
||||||
|
Expect(p.Config.Requirements).Should(ContainElement(req))
|
||||||
|
}
|
||||||
|
|
||||||
|
By("checking annotations")
|
||||||
|
// The creation timestamp is always present.
|
||||||
|
Expect(p.Manifest.Annotations).Should(HaveLen(len(annotations) + 1))
|
||||||
|
for key, val := range annotations {
|
||||||
|
Expect(p.Manifest.Annotations).Should(HaveKeyWithValue(key, val))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
By("checking tags")
|
||||||
|
Expect(pluginData.Tags).Should(HaveLen(len(pushedTags)))
|
||||||
|
Expect(pluginData.Tags).Should(ContainElements(pushedTags))
|
||||||
|
|
||||||
|
By("checking that temporary dirs have been removed")
|
||||||
|
|
||||||
|
Eventually(func() bool {
|
||||||
|
entries, err := os.ReadDir("/tmp")
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
for _, e := range entries {
|
||||||
|
if e.IsDir() {
|
||||||
|
matched, err := filepath.Match(utils.TmpDirPrefix+"*", regexp.QuoteMeta(e.Name()))
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
if matched {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}).WithTimeout(5 * time.Second).Should(BeFalse())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each test gets its own root command and runs it.
|
||||||
|
// The err variable is asserted by each test.
|
||||||
|
JustBeforeEach(func() {
|
||||||
|
rootCmd = cmd.New(ctx, opt)
|
||||||
|
err = executeRoot(args)
|
||||||
|
})
|
||||||
|
|
||||||
|
JustAfterEach(func() {
|
||||||
|
// Reset the status after each test.
|
||||||
|
// This variable could be changed by single tests.
|
||||||
|
// Make sure to set them at their default values.
|
||||||
|
Expect(output.Clear()).ShouldNot(HaveOccurred())
|
||||||
|
artifactNameInConfigLayer = "test-plugin"
|
||||||
|
pushedTags = []string{"tag1", "tag2", "latest"}
|
||||||
|
pluginOne = ""
|
||||||
|
pluginTwo = ""
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("success", func() {
|
||||||
|
JustBeforeEach(func() {
|
||||||
|
// Check the returned error before proceeding.
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
pluginData, err = testutils.FetchPluginFromRegistry(ctx, repoName, pushedTags[0], orasRegistry)
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
When("two platforms, with reqs and deps", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
repoName, fullRepoName = randomRulesRepoName(registry, pluginsRepoBaseName)
|
||||||
|
pluginOne = rulesfileyaml
|
||||||
|
pluginTwo = plugintgz
|
||||||
|
|
||||||
|
args = []string{registryCmd, pushCmd, fullRepoName, pluginOne, pluginTwo, "--type", "plugin", "--platform",
|
||||||
|
platformAMD64, "--platform", platformARM64, "--version", version, "--config", configFile,
|
||||||
|
"--plain-http", "--depends-on", "my-test:4.3.2", "--requires", requirement, "--annotation-source", anSource,
|
||||||
|
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
|
||||||
|
})
|
||||||
|
|
||||||
|
AssertSuccessBehaviour([]oci.ArtifactDependency{{
|
||||||
|
Name: "my-test",
|
||||||
|
Version: "4.3.2",
|
||||||
|
Alternatives: nil,
|
||||||
|
}}, []oci.ArtifactRequirement{
|
||||||
|
{
|
||||||
|
Name: "plugin_api_version",
|
||||||
|
Version: "3.2.1",
|
||||||
|
},
|
||||||
|
}, map[string]string{
|
||||||
|
"org.opencontainers.image.source": anSource,
|
||||||
|
}, []string{
|
||||||
|
platformAMD64, platformARM64,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
When("one platform, no reqs", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
repoName, fullRepoName = randomRulesRepoName(registry, pluginsRepoBaseName)
|
||||||
|
pluginOne = plugintgz
|
||||||
|
args = []string{registryCmd, pushCmd, fullRepoName, pluginOne, "--type", "plugin", "--platform",
|
||||||
|
platformAMD64, "--version", version, "--config", configFile,
|
||||||
|
"--plain-http", "--depends-on", "my-test:4.3.2", "--annotation-source", anSource,
|
||||||
|
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
|
||||||
|
})
|
||||||
|
// We expect to succeed and that the requirement is empty.
|
||||||
|
AssertSuccessBehaviour([]oci.ArtifactDependency{{
|
||||||
|
Name: "my-test",
|
||||||
|
Version: "4.3.2",
|
||||||
|
Alternatives: nil,
|
||||||
|
}}, []oci.ArtifactRequirement{}, map[string]string{
|
||||||
|
"org.opencontainers.image.source": anSource,
|
||||||
|
}, []string{
|
||||||
|
platformAMD64,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,655 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2024 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package push_test
|
||||||
|
|
||||||
|
// revive:disable
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/onsi/gomega/gbytes"
|
||||||
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
|
||||||
|
"github.com/falcosecurity/falcoctl/cmd"
|
||||||
|
"github.com/falcosecurity/falcoctl/internal/utils"
|
||||||
|
"github.com/falcosecurity/falcoctl/pkg/oci"
|
||||||
|
testutils "github.com/falcosecurity/falcoctl/pkg/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
// revive:enable
|
||||||
|
|
||||||
|
var _ = Describe("pushing rulesfiles", func() {
|
||||||
|
var (
|
||||||
|
registryCmd = "registry"
|
||||||
|
pushCmd = "push"
|
||||||
|
version = "1.1.1"
|
||||||
|
// registry/rulesRepoBaseName-randomInt
|
||||||
|
fullRepoName string
|
||||||
|
// rulesRepoBaseName-randomInt
|
||||||
|
repoName string
|
||||||
|
// It is set in the config layer.
|
||||||
|
artifactNameInConfigLayer = "test-rulesfile"
|
||||||
|
pushedTags = []string{"tag1", "tag2", "latest"}
|
||||||
|
|
||||||
|
// Variables passed as arguments to the push command. Each test case updates them
|
||||||
|
// to point to the file on disk living in pkg/test/data.
|
||||||
|
rulesfile string
|
||||||
|
|
||||||
|
// Data fetched from registry and used for assertions.
|
||||||
|
rulesfileData *testutils.RulesfileArtifact
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Used as flags for all the test cases.
|
||||||
|
dep1 = "myplugin:1.2.3"
|
||||||
|
dep2 = "myplugin1:1.2.3|otherplugin:3.2.1"
|
||||||
|
req = "engine_version_semver:0.37.0"
|
||||||
|
anSource = "myrepo.com/rules.git"
|
||||||
|
rulesRepoBaseName = "push-rulesfile"
|
||||||
|
)
|
||||||
|
|
||||||
|
// We keep it inside the success context since need the variables of this context.
|
||||||
|
var AssertSuccesBehaviour = func(deps []oci.ArtifactDependency, reqs []oci.ArtifactRequirement, annotations map[string]string) {
|
||||||
|
It("should succeed", func() {
|
||||||
|
// We do not check the error here since we are checking it after
|
||||||
|
// pushing the artifact.
|
||||||
|
By("checking no error in output")
|
||||||
|
Expect(output).ShouldNot(gbytes.Say("ERROR"))
|
||||||
|
Expect(output).ShouldNot(gbytes.Say("Unable to remove temporary dir"))
|
||||||
|
|
||||||
|
By("checking descriptor")
|
||||||
|
Expect(rulesfileData.Descriptor.MediaType).Should(Equal(v1.MediaTypeImageManifest))
|
||||||
|
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(rulesfileData.Descriptor.Digest.String())))
|
||||||
|
|
||||||
|
By("checking manifest")
|
||||||
|
Expect(rulesfileData.Layer.Manifest.Layers).Should(HaveLen(1))
|
||||||
|
|
||||||
|
By("checking platforms")
|
||||||
|
Expect(rulesfileData.Descriptor.Platform).Should(BeNil())
|
||||||
|
|
||||||
|
By("checking config layer")
|
||||||
|
Expect(rulesfileData.Layer.Config.Version).Should(Equal(version))
|
||||||
|
Expect(rulesfileData.Layer.Config.Name).Should(Equal(artifactNameInConfigLayer))
|
||||||
|
|
||||||
|
By("checking dependencies")
|
||||||
|
Expect(rulesfileData.Layer.Config.Dependencies).Should(HaveLen(len(deps)))
|
||||||
|
for _, dep := range deps {
|
||||||
|
Expect(rulesfileData.Layer.Config.Dependencies).Should(ContainElement(dep))
|
||||||
|
}
|
||||||
|
|
||||||
|
By("checking requirements")
|
||||||
|
Expect(rulesfileData.Layer.Config.Requirements).Should(HaveLen(len(reqs)))
|
||||||
|
for _, req := range reqs {
|
||||||
|
Expect(rulesfileData.Layer.Config.Requirements).Should(ContainElement(req))
|
||||||
|
}
|
||||||
|
|
||||||
|
By("checking annotations")
|
||||||
|
// The creation timestamp is always present.
|
||||||
|
Expect(rulesfileData.Layer.Manifest.Annotations).Should(HaveLen(len(annotations) + 1))
|
||||||
|
for key, val := range annotations {
|
||||||
|
Expect(rulesfileData.Layer.Manifest.Annotations).Should(HaveKeyWithValue(key, val))
|
||||||
|
}
|
||||||
|
|
||||||
|
By("checking tags")
|
||||||
|
Expect(rulesfileData.Tags).Should(HaveLen(len(pushedTags)))
|
||||||
|
Expect(rulesfileData.Tags).Should(ContainElements(pushedTags))
|
||||||
|
|
||||||
|
By("checking that temporary dirs have been removed")
|
||||||
|
Eventually(func() bool {
|
||||||
|
entries, err := os.ReadDir("/tmp")
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
for _, e := range entries {
|
||||||
|
if e.IsDir() {
|
||||||
|
matched, err := filepath.Match(utils.TmpDirPrefix+"*", regexp.QuoteMeta(e.Name()))
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
if matched {
|
||||||
|
fmt.Println(e.Name())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}).WithTimeout(5 * time.Second).Should(BeFalse())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each test gets its own root command and runs it.
|
||||||
|
// The err variable is asserted by each test.
|
||||||
|
JustBeforeEach(func() {
|
||||||
|
rootCmd = cmd.New(ctx, opt)
|
||||||
|
err = executeRoot(args)
|
||||||
|
})
|
||||||
|
|
||||||
|
JustAfterEach(func() {
|
||||||
|
Expect(output.Clear()).ShouldNot(HaveOccurred())
|
||||||
|
// This variable could be changed by single tests.
|
||||||
|
// Make sure to set them at their default values.
|
||||||
|
artifactNameInConfigLayer = "test-rulesfile"
|
||||||
|
pushedTags = []string{"tag1", "tag2", "latest"}
|
||||||
|
rulesfile = ""
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("success", func() {
|
||||||
|
// Here we are testing all the success cases for the push command. The artifact type used here is of type
|
||||||
|
// rulesfile. Keep in mind that here we are testing also the common flags that could be used by the plugin
|
||||||
|
// artifacts. So we are testing that common logic only once, and are doing it here.
|
||||||
|
|
||||||
|
JustBeforeEach(func() {
|
||||||
|
// This runs after the push command, so check the returned error before proceeding.
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
rulesfileData, err = testutils.FetchRulesfileFromRegistry(ctx, repoName, pushedTags[0], orasRegistry)
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName)
|
||||||
|
})
|
||||||
|
|
||||||
|
When("with full flags and args", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
rulesfile = rulesfileyaml
|
||||||
|
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
|
||||||
|
"--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req, "--annotation-source", anSource,
|
||||||
|
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
|
||||||
|
})
|
||||||
|
AssertSuccesBehaviour([]oci.ArtifactDependency{
|
||||||
|
{
|
||||||
|
Name: "myplugin",
|
||||||
|
Version: "1.2.3",
|
||||||
|
Alternatives: nil,
|
||||||
|
}, {
|
||||||
|
Name: "myplugin1",
|
||||||
|
Version: "1.2.3",
|
||||||
|
Alternatives: []oci.Dependency{{
|
||||||
|
Name: "otherplugin",
|
||||||
|
Version: "3.2.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, []oci.ArtifactRequirement{
|
||||||
|
{
|
||||||
|
Name: "engine_version_semver",
|
||||||
|
Version: "0.37.0",
|
||||||
|
},
|
||||||
|
}, map[string]string{
|
||||||
|
"org.opencontainers.image.source": anSource,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
When("no --name flag provided", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
rulesfile = rulesfileyaml
|
||||||
|
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
|
||||||
|
"--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req, "--annotation-source", anSource,
|
||||||
|
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2]}
|
||||||
|
// Set name to the expected one.
|
||||||
|
artifactNameInConfigLayer = repoName
|
||||||
|
})
|
||||||
|
|
||||||
|
AssertSuccesBehaviour([]oci.ArtifactDependency{
|
||||||
|
{
|
||||||
|
Name: "myplugin",
|
||||||
|
Version: "1.2.3",
|
||||||
|
Alternatives: nil,
|
||||||
|
}, {
|
||||||
|
Name: "myplugin1",
|
||||||
|
Version: "1.2.3",
|
||||||
|
Alternatives: []oci.Dependency{{
|
||||||
|
Name: "otherplugin",
|
||||||
|
Version: "3.2.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, []oci.ArtifactRequirement{
|
||||||
|
{
|
||||||
|
Name: "engine_version_semver",
|
||||||
|
Version: "0.37.0",
|
||||||
|
},
|
||||||
|
}, map[string]string{
|
||||||
|
"org.opencontainers.image.source": anSource,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
When("no --annotation-source provided", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
rulesfile = rulesfileyaml
|
||||||
|
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
|
||||||
|
"--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req,
|
||||||
|
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
|
||||||
|
})
|
||||||
|
AssertSuccesBehaviour([]oci.ArtifactDependency{
|
||||||
|
{
|
||||||
|
Name: "myplugin",
|
||||||
|
Version: "1.2.3",
|
||||||
|
Alternatives: nil,
|
||||||
|
}, {
|
||||||
|
Name: "myplugin1",
|
||||||
|
Version: "1.2.3",
|
||||||
|
Alternatives: []oci.Dependency{{
|
||||||
|
Name: "otherplugin",
|
||||||
|
Version: "3.2.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, []oci.ArtifactRequirement{
|
||||||
|
{
|
||||||
|
Name: "engine_version_semver",
|
||||||
|
Version: "0.37.0",
|
||||||
|
},
|
||||||
|
}, map[string]string{})
|
||||||
|
})
|
||||||
|
|
||||||
|
When("no --tags provided", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
rulesfile = rulesfileyaml
|
||||||
|
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
|
||||||
|
"--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req, "--annotation-source", anSource,
|
||||||
|
"--name", artifactNameInConfigLayer}
|
||||||
|
// We expect that latest tag is pushed, so set it in the pushed tags.
|
||||||
|
pushedTags = []string{"latest"}
|
||||||
|
})
|
||||||
|
AssertSuccesBehaviour([]oci.ArtifactDependency{
|
||||||
|
{
|
||||||
|
Name: "myplugin",
|
||||||
|
Version: "1.2.3",
|
||||||
|
Alternatives: nil,
|
||||||
|
}, {
|
||||||
|
Name: "myplugin1",
|
||||||
|
Version: "1.2.3",
|
||||||
|
Alternatives: []oci.Dependency{{
|
||||||
|
Name: "otherplugin",
|
||||||
|
Version: "3.2.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, []oci.ArtifactRequirement{
|
||||||
|
{
|
||||||
|
Name: "engine_version_semver",
|
||||||
|
Version: "0.37.0",
|
||||||
|
},
|
||||||
|
}, map[string]string{
|
||||||
|
"org.opencontainers.image.source": anSource,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
When("no --depends-on flag provided", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
rulesfile = rulesfileyaml
|
||||||
|
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
|
||||||
|
"--plain-http", "--requires", req, "--annotation-source", anSource,
|
||||||
|
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
|
||||||
|
})
|
||||||
|
AssertSuccesBehaviour([]oci.ArtifactDependency{},
|
||||||
|
[]oci.ArtifactRequirement{
|
||||||
|
{
|
||||||
|
Name: "engine_version_semver",
|
||||||
|
Version: "0.37.0",
|
||||||
|
},
|
||||||
|
}, map[string]string{
|
||||||
|
"org.opencontainers.image.source": anSource,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
When("no --requires flag provided", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
rulesfile = rulesfileyaml
|
||||||
|
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
|
||||||
|
"--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--annotation-source", anSource,
|
||||||
|
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
|
||||||
|
})
|
||||||
|
AssertSuccesBehaviour([]oci.ArtifactDependency{
|
||||||
|
{
|
||||||
|
Name: "myplugin",
|
||||||
|
Version: "1.2.3",
|
||||||
|
Alternatives: nil,
|
||||||
|
}, {
|
||||||
|
Name: "myplugin1",
|
||||||
|
Version: "1.2.3",
|
||||||
|
Alternatives: []oci.Dependency{{
|
||||||
|
Name: "otherplugin",
|
||||||
|
Version: "3.2.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, []oci.ArtifactRequirement{}, map[string]string{
|
||||||
|
"org.opencontainers.image.source": anSource,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
When("only required flags", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
rulesfile = rulesfileyaml
|
||||||
|
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
|
||||||
|
"--plain-http"}
|
||||||
|
// Set name to the expected one.
|
||||||
|
artifactNameInConfigLayer = repoName
|
||||||
|
// We expect that latest tag is pushed, so set it in the pushed tags.
|
||||||
|
pushedTags = []string{"latest"}
|
||||||
|
})
|
||||||
|
AssertSuccesBehaviour([]oci.ArtifactDependency{},
|
||||||
|
[]oci.ArtifactRequirement{},
|
||||||
|
map[string]string{})
|
||||||
|
})
|
||||||
|
|
||||||
|
When("with add-floating-tags and the required flags", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
rulesfile = rulesfileyaml
|
||||||
|
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
|
||||||
|
"--add-floating-tags", "--plain-http"}
|
||||||
|
// Set name to the expected one.
|
||||||
|
artifactNameInConfigLayer = repoName
|
||||||
|
// The semver tags are expected to be set.
|
||||||
|
pushedTags = []string{"1.1.1", "1.1", "1"}
|
||||||
|
})
|
||||||
|
AssertSuccesBehaviour([]oci.ArtifactDependency{},
|
||||||
|
[]oci.ArtifactRequirement{},
|
||||||
|
map[string]string{})
|
||||||
|
})
|
||||||
|
|
||||||
|
When("with full flags and args but in tar.gz format", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
rulesfile = rulesfiletgz
|
||||||
|
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
|
||||||
|
"--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req, "--annotation-source", anSource,
|
||||||
|
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
|
||||||
|
})
|
||||||
|
AssertSuccesBehaviour([]oci.ArtifactDependency{
|
||||||
|
{
|
||||||
|
Name: "myplugin",
|
||||||
|
Version: "1.2.3",
|
||||||
|
Alternatives: nil,
|
||||||
|
}, {
|
||||||
|
Name: "myplugin1",
|
||||||
|
Version: "1.2.3",
|
||||||
|
Alternatives: []oci.Dependency{{
|
||||||
|
Name: "otherplugin",
|
||||||
|
Version: "3.2.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, []oci.ArtifactRequirement{
|
||||||
|
{
|
||||||
|
Name: "engine_version_semver",
|
||||||
|
Version: "0.37.0",
|
||||||
|
},
|
||||||
|
}, map[string]string{
|
||||||
|
"org.opencontainers.image.source": anSource,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("rulesfile deps and requirements", func() {
|
||||||
|
When("user provided deps", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName)
|
||||||
|
rulesfile = rulesFileWithDepsAndReq
|
||||||
|
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
|
||||||
|
"--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req, "--annotation-source", anSource,
|
||||||
|
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
|
||||||
|
})
|
||||||
|
|
||||||
|
AssertSuccesBehaviour([]oci.ArtifactDependency{
|
||||||
|
{
|
||||||
|
Name: "myplugin",
|
||||||
|
Version: "1.2.3",
|
||||||
|
Alternatives: nil,
|
||||||
|
}, {
|
||||||
|
Name: "myplugin1",
|
||||||
|
Version: "1.2.3",
|
||||||
|
Alternatives: []oci.Dependency{{
|
||||||
|
Name: "otherplugin",
|
||||||
|
Version: "3.2.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, []oci.ArtifactRequirement{
|
||||||
|
{
|
||||||
|
Name: "engine_version_semver",
|
||||||
|
Version: "0.37.0",
|
||||||
|
},
|
||||||
|
}, map[string]string{
|
||||||
|
"org.opencontainers.image.source": anSource,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
When("parsed from file deps", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName)
|
||||||
|
rulesfile = rulesFileWithDepsAndReq
|
||||||
|
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
|
||||||
|
"--plain-http", "--annotation-source", anSource,
|
||||||
|
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
|
||||||
|
})
|
||||||
|
|
||||||
|
AssertSuccesBehaviour([]oci.ArtifactDependency{
|
||||||
|
{
|
||||||
|
Name: "cloudtrail",
|
||||||
|
Version: "0.2.3",
|
||||||
|
Alternatives: nil,
|
||||||
|
}, {
|
||||||
|
Name: "json",
|
||||||
|
Version: "0.2.2",
|
||||||
|
Alternatives: nil,
|
||||||
|
},
|
||||||
|
}, []oci.ArtifactRequirement{
|
||||||
|
{
|
||||||
|
Name: "engine_version_semver",
|
||||||
|
Version: "0.10.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"org.opencontainers.image.source": anSource,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
When("parsed from file deps with alternatives", func() {
|
||||||
|
var data = `
|
||||||
|
- required_plugin_versions:
|
||||||
|
- name: k8saudit
|
||||||
|
version: 0.7.0
|
||||||
|
alternatives:
|
||||||
|
- name: k8saudit-eks
|
||||||
|
version: 0.4.0
|
||||||
|
- name: json
|
||||||
|
version: 0.7.0
|
||||||
|
`
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName)
|
||||||
|
tmpDir := GinkgoT().TempDir()
|
||||||
|
rulesfile, err = testutils.WriteToTmpFile(data, tmpDir)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
|
||||||
|
"--plain-http", "--annotation-source", anSource,
|
||||||
|
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
|
||||||
|
})
|
||||||
|
|
||||||
|
AssertSuccesBehaviour([]oci.ArtifactDependency{
|
||||||
|
{
|
||||||
|
Name: "json",
|
||||||
|
Version: "0.7.0",
|
||||||
|
Alternatives: nil,
|
||||||
|
}, {
|
||||||
|
Name: "k8saudit",
|
||||||
|
Version: "0.7.0",
|
||||||
|
Alternatives: []oci.Dependency{{
|
||||||
|
Name: "k8saudit-eks",
|
||||||
|
Version: "0.4.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, []oci.ArtifactRequirement{},
|
||||||
|
map[string]string{
|
||||||
|
"org.opencontainers.image.source": anSource,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
When("no deps at all", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName)
|
||||||
|
rulesfile = rulesfileyaml
|
||||||
|
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
|
||||||
|
"--plain-http", "--annotation-source", anSource,
|
||||||
|
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
|
||||||
|
})
|
||||||
|
|
||||||
|
AssertSuccesBehaviour([]oci.ArtifactDependency{}, []oci.ArtifactRequirement{},
|
||||||
|
map[string]string{
|
||||||
|
"org.opencontainers.image.source": anSource,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
When("user provided requirement", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName)
|
||||||
|
rulesfile = rulesFileWithDepsAndReq
|
||||||
|
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
|
||||||
|
"--plain-http", "--requires", req, "--annotation-source", anSource,
|
||||||
|
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
|
||||||
|
})
|
||||||
|
|
||||||
|
AssertSuccesBehaviour([]oci.ArtifactDependency{
|
||||||
|
{
|
||||||
|
Name: "json",
|
||||||
|
Version: "0.2.2",
|
||||||
|
Alternatives: nil,
|
||||||
|
}, {
|
||||||
|
Name: "cloudtrail",
|
||||||
|
Version: "0.2.3",
|
||||||
|
Alternatives: nil,
|
||||||
|
},
|
||||||
|
}, []oci.ArtifactRequirement{
|
||||||
|
{
|
||||||
|
Name: "engine_version_semver",
|
||||||
|
Version: "0.37.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"org.opencontainers.image.source": anSource,
|
||||||
|
})
|
||||||
|
It("reqs should be the ones provided by the user", func() {
|
||||||
|
Expect(fmt.Sprintf("%s:%s", rulesfileData.Layer.Config.Requirements[0].Name,
|
||||||
|
rulesfileData.Layer.Config.Requirements[0].Version)).Should(Equal(req))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
When("requirement parsed from file in semver format", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName)
|
||||||
|
rulesfile = rulesFileWithDepsAndReq
|
||||||
|
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
|
||||||
|
"--plain-http", "--annotation-source", anSource,
|
||||||
|
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
|
||||||
|
})
|
||||||
|
|
||||||
|
AssertSuccesBehaviour([]oci.ArtifactDependency{
|
||||||
|
{
|
||||||
|
Name: "json",
|
||||||
|
Version: "0.2.2",
|
||||||
|
Alternatives: nil,
|
||||||
|
}, {
|
||||||
|
Name: "cloudtrail",
|
||||||
|
Version: "0.2.3",
|
||||||
|
Alternatives: nil,
|
||||||
|
},
|
||||||
|
}, []oci.ArtifactRequirement{
|
||||||
|
{
|
||||||
|
Name: "engine_version_semver",
|
||||||
|
Version: "0.10.0",
|
||||||
|
},
|
||||||
|
}, map[string]string{
|
||||||
|
"org.opencontainers.image.source": anSource,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
When("requirement parsed from file in int format", func() {
|
||||||
|
var rulesfileContent = `
|
||||||
|
- required_engine_version: 10
|
||||||
|
`
|
||||||
|
BeforeEach(func() {
|
||||||
|
repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName)
|
||||||
|
tmpDir := GinkgoT().TempDir()
|
||||||
|
rulesfile, err = testutils.WriteToTmpFile(rulesfileContent, tmpDir)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
|
||||||
|
"--plain-http", "--annotation-source", anSource,
|
||||||
|
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
|
||||||
|
})
|
||||||
|
|
||||||
|
AssertSuccesBehaviour([]oci.ArtifactDependency{}, []oci.ArtifactRequirement{
|
||||||
|
{
|
||||||
|
Name: "engine_version_semver",
|
||||||
|
Version: "0.10.0",
|
||||||
|
},
|
||||||
|
}, map[string]string{
|
||||||
|
"org.opencontainers.image.source": anSource,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("failure", func() {
|
||||||
|
When("requirement parsed from file -- invalid format (float)", func() {
|
||||||
|
var rulesFile = `
|
||||||
|
- required_engine_version: 10.0
|
||||||
|
`
|
||||||
|
BeforeEach(func() {
|
||||||
|
repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName)
|
||||||
|
tmpDir := GinkgoT().TempDir()
|
||||||
|
rulesfile, err = testutils.WriteToTmpFile(rulesFile, tmpDir)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
|
||||||
|
"--plain-http", "--annotation-source", anSource,
|
||||||
|
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should fail", func() {
|
||||||
|
Expect(err).Should(HaveOccurred())
|
||||||
|
Expect(output).Should(gbytes.Say(regexp.QuoteMeta("required_engine_version must be an int or a string respecting " +
|
||||||
|
"the semver specification, got type float64")))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
When("requirement parsed from file -- invalid format (not semver)", func() {
|
||||||
|
var rulesFile = `
|
||||||
|
- required_engine_version: 10.0notsemver
|
||||||
|
`
|
||||||
|
BeforeEach(func() {
|
||||||
|
repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName)
|
||||||
|
tmpDir := GinkgoT().TempDir()
|
||||||
|
rulesfile, err = testutils.WriteToTmpFile(rulesFile, tmpDir)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
|
||||||
|
"--plain-http", "--annotation-source", anSource,
|
||||||
|
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
|
||||||
|
// Set name to the expected one.
|
||||||
|
artifactNameInConfigLayer = repoName
|
||||||
|
// We expect that latest tag is pushed, so set it in the pushed tags.
|
||||||
|
pushedTags = []string{"latest"}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("reqs should be the ones provided by the user", func() {
|
||||||
|
Expect(err).Should(HaveOccurred())
|
||||||
|
Expect(output).Should(gbytes.Say(regexp.QuoteMeta("10.0notsemver must be in semver format: No Major.Minor.Patch elements found")))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2023 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -17,9 +18,11 @@ package push_test
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/distribution/distribution/v3/configuration"
|
"github.com/distribution/distribution/v3/configuration"
|
||||||
_ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
|
_ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
|
||||||
|
@ -34,14 +37,13 @@ import (
|
||||||
testutils "github.com/falcosecurity/falcoctl/pkg/test"
|
testutils "github.com/falcosecurity/falcoctl/pkg/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint:unused // false positive
|
|
||||||
const (
|
const (
|
||||||
rulesfiletgz = "../../../pkg/test/data/rules.tar.gz"
|
rulesfiletgz = "../../../pkg/test/data/rules.tar.gz"
|
||||||
rulesfileyaml = "../../../pkg/test/data/rules.yaml"
|
rulesfileyaml = "../../../pkg/test/data/rulesWithoutReqAndDeps.yaml"
|
||||||
|
rulesFileWithDepsAndReq = "../../../pkg/test/data/rules.yaml"
|
||||||
plugintgz = "../../../pkg/test/data/plugin.tar.gz"
|
plugintgz = "../../../pkg/test/data/plugin.tar.gz"
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint:unused // false positive
|
|
||||||
var (
|
var (
|
||||||
registry string
|
registry string
|
||||||
ctx = context.Background()
|
ctx = context.Background()
|
||||||
|
@ -70,7 +72,6 @@ var _ = BeforeSuite(func() {
|
||||||
// Create and configure the common options.
|
// Create and configure the common options.
|
||||||
opt = commonoptions.NewOptions()
|
opt = commonoptions.NewOptions()
|
||||||
opt.Initialize(commonoptions.WithWriter(output))
|
opt.Initialize(commonoptions.WithWriter(output))
|
||||||
opt.Printer.DisableStylingf()
|
|
||||||
|
|
||||||
// Create the oras registry.
|
// Create the oras registry.
|
||||||
orasRegistry, err = testutils.NewOrasRegistry(registry, true)
|
orasRegistry, err = testutils.NewOrasRegistry(registry, true)
|
||||||
|
@ -82,6 +83,14 @@ var _ = BeforeSuite(func() {
|
||||||
Expect(err).ToNot(BeNil())
|
Expect(err).ToNot(BeNil())
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// Check that the registry is up and accepting connections.
|
||||||
|
Eventually(func(g Gomega) error {
|
||||||
|
res, err := http.Get(fmt.Sprintf("http://%s", config.HTTP.Addr))
|
||||||
|
g.Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
g.Expect(res.StatusCode).Should(Equal(http.StatusOK))
|
||||||
|
return err
|
||||||
|
}).WithTimeout(time.Second * 5).ShouldNot(HaveOccurred())
|
||||||
|
|
||||||
// Create temporary directory used to save the configuration file.
|
// Create temporary directory used to save the configuration file.
|
||||||
configFile, err = testutils.CreateEmptyFile("falcoctl.yaml")
|
configFile, err = testutils.CreateEmptyFile("falcoctl.yaml")
|
||||||
Expect(err).Should(Succeed())
|
Expect(err).Should(Succeed())
|
||||||
|
@ -92,9 +101,8 @@ var _ = AfterSuite(func() {
|
||||||
Expect(os.RemoveAll(configDir)).Should(Succeed())
|
Expect(os.RemoveAll(configDir)).Should(Succeed())
|
||||||
})
|
})
|
||||||
|
|
||||||
//nolint:unused // false positive
|
|
||||||
func executeRoot(args []string) error {
|
func executeRoot(args []string) error {
|
||||||
rootCmd.SetArgs(args)
|
rootCmd.SetArgs(args)
|
||||||
rootCmd.SetOut(output)
|
rootCmd.SetOut(output)
|
||||||
return cmd.Execute(rootCmd, opt.Printer)
|
return cmd.Execute(rootCmd, opt)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2023 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -17,25 +18,20 @@ package push_test
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
"github.com/onsi/gomega/gbytes"
|
"github.com/onsi/gomega/gbytes"
|
||||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
|
||||||
|
|
||||||
"github.com/falcosecurity/falcoctl/cmd"
|
"github.com/falcosecurity/falcoctl/cmd"
|
||||||
"github.com/falcosecurity/falcoctl/internal/utils"
|
|
||||||
testutils "github.com/falcosecurity/falcoctl/pkg/test"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint:lll,unused // no need to check for line length.
|
|
||||||
var registryPushUsage = `Usage:
|
var registryPushUsage = `Usage:
|
||||||
falcoctl registry push hostname/repo[:tag|@digest] file [flags]
|
falcoctl registry push hostname/repo[:tag|@digest] file [flags]
|
||||||
|
|
||||||
Flags:
|
Flags:
|
||||||
|
--add-floating-tags add the floating tags for the major and minor versions
|
||||||
--annotation-source string set annotation source for the artifact
|
--annotation-source string set annotation source for the artifact
|
||||||
-d, --depends-on stringArray set an artifact dependency (can be specified multiple times). Example: "--depends-on my-plugin:1.2.3"
|
-d, --depends-on stringArray set an artifact dependency (can be specified multiple times). Example: "--depends-on my-plugin:1.2.3"
|
||||||
-h, --help help for push
|
-h, --help help for push
|
||||||
|
@ -44,14 +40,13 @@ Flags:
|
||||||
--platform stringArray os and architecture of the artifact in OS/ARCH format (only for plugins artifacts)
|
--platform stringArray os and architecture of the artifact in OS/ARCH format (only for plugins artifacts)
|
||||||
-r, --requires stringArray set an artifact requirement (can be specified multiple times). Example: "--requires plugin_api_version:1.2.3"
|
-r, --requires stringArray set an artifact requirement (can be specified multiple times). Example: "--requires plugin_api_version:1.2.3"
|
||||||
-t, --tag stringArray additional artifact tag. Can be repeated multiple times
|
-t, --tag stringArray additional artifact tag. Can be repeated multiple times
|
||||||
--type ArtifactType type of artifact to be pushed. Allowed values: "rulesfile", "plugin" (default )
|
--type ArtifactType type of artifact to be pushed. Allowed values: "rulesfile", "plugin", "asset" (default )
|
||||||
--version string set the version of the artifact
|
--version string set the version of the artifact
|
||||||
|
|
||||||
Global Flags:
|
Global Flags:
|
||||||
--config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml")
|
--config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml")
|
||||||
--disable-styling Disable output styling such as spinners, progress bars and colors. Styling is automatically disabled if not attacched to a tty (default false)
|
--log-format string Set formatting for logs (color, text, json) (default "color")
|
||||||
-v, --verbose Enable verbose logs (default false)
|
--log-level string Set level for logs (info, warn, debug, trace) (default "info")
|
||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
//nolint:lll,unused // no need to check for line length.
|
//nolint:lll,unused // no need to check for line length.
|
||||||
|
@ -71,6 +66,10 @@ Example - Push artifact "myplugin.tar.gz" of type "plugin" for multiple platform
|
||||||
Example - Push artifact "myrulesfile.tar.gz" of type "rulesfile":
|
Example - Push artifact "myrulesfile.tar.gz" of type "rulesfile":
|
||||||
falcoctl registry push --type rulesfile --version "0.1.2" localhost:5000/myrulesfile:latest myrulesfile.tar.gz
|
falcoctl registry push --type rulesfile --version "0.1.2" localhost:5000/myrulesfile:latest myrulesfile.tar.gz
|
||||||
|
|
||||||
|
Example - Push artifact "myrulesfile.tar.gz" of type "rulesfile" with floating tags for the major and minor versions (0 and 0.1):
|
||||||
|
falcoctl registry push --type rulesfile --version "0.1.2" localhost:5000/myrulesfile:latest myrulesfile.tar.gz \
|
||||||
|
--add-floating-tags
|
||||||
|
|
||||||
Example - Push artifact "myrulesfile.tar.gz" of type "rulesfile" to an insecure registry:
|
Example - Push artifact "myrulesfile.tar.gz" of type "rulesfile" to an insecure registry:
|
||||||
falcoctl registry push --type rulesfile --version "0.1.2" --plain-http localhost:5000/myrulesfile:latest myrulesfile.tar.gz
|
falcoctl registry push --type rulesfile --version "0.1.2" --plain-http localhost:5000/myrulesfile:latest myrulesfile.tar.gz
|
||||||
|
|
||||||
|
@ -91,6 +90,7 @@ Usage:
|
||||||
falcoctl registry push hostname/repo[:tag|@digest] file [flags]
|
falcoctl registry push hostname/repo[:tag|@digest] file [flags]
|
||||||
|
|
||||||
Flags:
|
Flags:
|
||||||
|
--add-floating-tags add the floating tags for the major and minor versions
|
||||||
--annotation-source string set annotation source for the artifact
|
--annotation-source string set annotation source for the artifact
|
||||||
-d, --depends-on stringArray set an artifact dependency (can be specified multiple times). Example: "--depends-on my-plugin:1.2.3"
|
-d, --depends-on stringArray set an artifact dependency (can be specified multiple times). Example: "--depends-on my-plugin:1.2.3"
|
||||||
-h, --help help for push
|
-h, --help help for push
|
||||||
|
@ -99,16 +99,15 @@ Flags:
|
||||||
--platform stringArray os and architecture of the artifact in OS/ARCH format (only for plugins artifacts)
|
--platform stringArray os and architecture of the artifact in OS/ARCH format (only for plugins artifacts)
|
||||||
-r, --requires stringArray set an artifact requirement (can be specified multiple times). Example: "--requires plugin_api_version:1.2.3"
|
-r, --requires stringArray set an artifact requirement (can be specified multiple times). Example: "--requires plugin_api_version:1.2.3"
|
||||||
-t, --tag stringArray additional artifact tag. Can be repeated multiple times
|
-t, --tag stringArray additional artifact tag. Can be repeated multiple times
|
||||||
--type ArtifactType type of artifact to be pushed. Allowed values: "rulesfile", "plugin"
|
--type ArtifactType type of artifact to be pushed. Allowed values: "rulesfile", "plugin", "asset"
|
||||||
--version string set the version of the artifact
|
--version string set the version of the artifact
|
||||||
|
|
||||||
Global Flags:
|
Global Flags:
|
||||||
--config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml")
|
--config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml")
|
||||||
--disable-styling Disable output styling such as spinners, progress bars and colors. Styling is automatically disabled if not attacched to a tty (default false)
|
--log-format string Set formatting for logs (color, text, json) (default "color")
|
||||||
-v, --verbose Enable verbose logs (default false)
|
--log-level string Set level for logs (info, warn, debug, trace) (default "info")
|
||||||
`
|
`
|
||||||
|
|
||||||
//nolint:unused // false positive
|
|
||||||
var pushAssertFailedBehavior = func(usage, specificError string) {
|
var pushAssertFailedBehavior = func(usage, specificError string) {
|
||||||
It("check that fails and the usage is not printed", func() {
|
It("check that fails and the usage is not printed", func() {
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(err).To(HaveOccurred())
|
||||||
|
@ -117,27 +116,17 @@ var pushAssertFailedBehavior = func(usage, specificError string) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:unused // false positive
|
|
||||||
var randomRulesRepoName = func(registry, repo string) (string, string) {
|
var randomRulesRepoName = func(registry, repo string) (string, string) {
|
||||||
rName := fmt.Sprintf("%s-%d", repo, rand.Int())
|
rName := fmt.Sprintf("%s-%d", repo, rand.Int())
|
||||||
return rName, fmt.Sprintf("%s/%s", registry, rName)
|
return rName, fmt.Sprintf("%s/%s", registry, rName)
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:unused // false positive
|
var _ = Describe("push", func() {
|
||||||
var registryPushTests = Describe("push", func() {
|
|
||||||
var (
|
var (
|
||||||
registryCmd = "registry"
|
registryCmd = "registry"
|
||||||
pushCmd = "push"
|
pushCmd = "push"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// Used as flags for all the test cases.
|
|
||||||
dep1 = "myplugin:1.2.3"
|
|
||||||
dep2 = "myplugin1:1.2.3|otherplugin:3.2.1"
|
|
||||||
req = "engine_version:15"
|
|
||||||
anSource = "myrepo.com/rules.git"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Each test gets its own root command and runs it.
|
// Each test gets its own root command and runs it.
|
||||||
// The err variable is asserted by each test.
|
// The err variable is asserted by each test.
|
||||||
JustBeforeEach(func() {
|
JustBeforeEach(func() {
|
||||||
|
@ -155,8 +144,8 @@ var registryPushTests = Describe("push", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should match the saved one", func() {
|
It("should match the saved one", func() {
|
||||||
|
outputMsg := string(output.Contents())
|
||||||
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(registryPushHelp)))
|
Expect(outputMsg).Should(Equal(registryPushHelp))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -173,29 +162,29 @@ var registryPushTests = Describe("push", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
args = []string{registryCmd, pushCmd, "--config", configFile, rulesRepo, rulesfiletgz, "--type", "rulesfile"}
|
args = []string{registryCmd, pushCmd, "--config", configFile, rulesRepo, rulesfiletgz, "--type", "rulesfile"}
|
||||||
})
|
})
|
||||||
pushAssertFailedBehavior(registryPushUsage, "ERRO: required flag(s) \"version\" not set\n")
|
pushAssertFailedBehavior(registryPushUsage, "ERROR required flag(s) \"version\" not set")
|
||||||
})
|
})
|
||||||
|
|
||||||
When("without rulesfile", func() {
|
When("without rulesfile", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
args = []string{registryCmd, pushCmd, "--config", configFile, rulesRepo, "--type", "rulesfile"}
|
args = []string{registryCmd, pushCmd, "--config", configFile, rulesRepo, "--type", "rulesfile"}
|
||||||
})
|
})
|
||||||
pushAssertFailedBehavior(registryPushUsage, "ERRO: requires at least 2 arg(s), only received 1\n")
|
pushAssertFailedBehavior(registryPushUsage, "ERROR requires at least 2 arg(s), only received 1")
|
||||||
})
|
})
|
||||||
|
|
||||||
When("without registry", func() {
|
When("without registry", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
args = []string{registryCmd, pushCmd, "--config", configFile, rulesfiletgz, "--type", "rulesfile"}
|
args = []string{registryCmd, pushCmd, "--config", configFile, rulesfiletgz, "--type", "rulesfile"}
|
||||||
})
|
})
|
||||||
pushAssertFailedBehavior(registryPushUsage, "ERRO: requires at least 2 arg(s), only received 1\n")
|
pushAssertFailedBehavior(registryPushUsage, "ERROR requires at least 2 arg(s), only received 1")
|
||||||
})
|
})
|
||||||
|
|
||||||
When("multiple rulesfiles", func() {
|
When("multiple rulesfiles", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
args = []string{registryCmd, pushCmd, rulesRepo, "--config", configFile, rulesfiletgz, rulesfiletgz,
|
args = []string{registryCmd, pushCmd, "--config", configFile,
|
||||||
"--type", "rulesfile", "--version", "1.1.1", "--plain-http"}
|
"--type", "rulesfile", "--version", "1.1.1", "--plain-http", rulesRepo, rulesfiletgz, rulesfiletgz}
|
||||||
})
|
})
|
||||||
pushAssertFailedBehavior(registryPushUsage, "ERRO: expecting 1 rulesfile object received 2: invalid number of rulesfiles\n")
|
pushAssertFailedBehavior(registryPushUsage, "ERROR expecting 1 rulesfile object, received 2: invalid number of rulesfiles")
|
||||||
})
|
})
|
||||||
|
|
||||||
When("unreachable registry", func() {
|
When("unreachable registry", func() {
|
||||||
|
@ -203,15 +192,31 @@ var registryPushTests = Describe("push", func() {
|
||||||
args = []string{registryCmd, pushCmd, "noregistry/testrules", "--config", configFile, rulesfiletgz,
|
args = []string{registryCmd, pushCmd, "noregistry/testrules", "--config", configFile, rulesfiletgz,
|
||||||
"--type", "rulesfile", "--version", "1.1.1", "--plain-http"}
|
"--type", "rulesfile", "--version", "1.1.1", "--plain-http"}
|
||||||
})
|
})
|
||||||
pushAssertFailedBehavior(registryPushUsage, "ERRO: unable to connect to remote "+
|
pushAssertFailedBehavior(registryPushUsage, "ERROR unable to connect to remote "+
|
||||||
"registry \"noregistry\": Get \"http://noregistry/v2/\": dial tcp: lookup noregistry")
|
"registry \"noregistry\": Get \"http://noregistry/v2/\": dial tcp: lookup noregistry")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
When("wrong semver for --version flag with --add-floating-tags", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{registryCmd, pushCmd, rulesRepo, rulesfiletgz, "--config", configFile, "--type", "rulesfile",
|
||||||
|
"--version", "notSemVer", "--add-floating-tags", "--plain-http"}
|
||||||
|
})
|
||||||
|
pushAssertFailedBehavior(registryPushUsage, "ERROR expected semver for the flag \"--version\": No Major.Minor.Patch elements found")
|
||||||
|
})
|
||||||
|
|
||||||
|
When("invalid character in semver for --version flag with --add-floating-tags", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
args = []string{registryCmd, pushCmd, rulesRepo, rulesfiletgz, "--config", configFile, "--type", "rulesfile",
|
||||||
|
"--version", "1.1.a", "--add-floating-tags", "--plain-http"}
|
||||||
|
})
|
||||||
|
pushAssertFailedBehavior(registryPushUsage, "ERROR expected semver for the flag \"--version\": Invalid character(s) found in patch number \"a\"")
|
||||||
|
})
|
||||||
|
|
||||||
When("missing repository", func() {
|
When("missing repository", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
args = []string{registryCmd, pushCmd, registry, rulesfiletgz, "--config", configFile, "--type", "rulesfile", "--version", "1.1.1", "--plain-http"}
|
args = []string{registryCmd, pushCmd, registry, rulesfiletgz, "--config", configFile, "--type", "rulesfile", "--version", "1.1.1", "--plain-http"}
|
||||||
})
|
})
|
||||||
pushAssertFailedBehavior(registryPushUsage, fmt.Sprintf("ERRO: cannot extract registry name from ref %q", registry))
|
pushAssertFailedBehavior(registryPushUsage, fmt.Sprintf("ERROR cannot extract registry name from ref %q", registry))
|
||||||
})
|
})
|
||||||
|
|
||||||
When("invalid repository", func() {
|
When("invalid repository", func() {
|
||||||
|
@ -219,8 +224,8 @@ var registryPushTests = Describe("push", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
args = []string{registryCmd, pushCmd, newReg, rulesfiletgz, "--config", configFile, "--type", "rulesfile", "--version", "1.1.1", "--plain-http"}
|
args = []string{registryCmd, pushCmd, newReg, rulesfiletgz, "--config", configFile, "--type", "rulesfile", "--version", "1.1.1", "--plain-http"}
|
||||||
})
|
})
|
||||||
pushAssertFailedBehavior(registryPushUsage, fmt.Sprintf("ERRO: unable to create new repository with ref %s: "+
|
pushAssertFailedBehavior(registryPushUsage, fmt.Sprintf("ERROR unable to create new repository with ref %s: "+
|
||||||
"invalid reference: invalid digest; invalid checksum digest format\n", newReg))
|
"invalid reference: invalid digest %q: invalid checksum digest format\n", newReg, "something"))
|
||||||
})
|
})
|
||||||
|
|
||||||
When("invalid requirement", func() {
|
When("invalid requirement", func() {
|
||||||
|
@ -228,7 +233,7 @@ var registryPushTests = Describe("push", func() {
|
||||||
args = []string{registryCmd, pushCmd, rulesRepo, rulesfiletgz, "--config", configFile, "--type", "rulesfile", "--version", "1.1.1",
|
args = []string{registryCmd, pushCmd, rulesRepo, rulesfiletgz, "--config", configFile, "--type", "rulesfile", "--version", "1.1.1",
|
||||||
"--plain-http", "--requires", "wrongreq"}
|
"--plain-http", "--requires", "wrongreq"}
|
||||||
})
|
})
|
||||||
pushAssertFailedBehavior(registryPushUsage, "ERRO: cannot parse \"wrongreq\"\n")
|
pushAssertFailedBehavior(registryPushUsage, "ERROR cannot parse \"wrongreq\"")
|
||||||
})
|
})
|
||||||
|
|
||||||
When("invalid dependency", func() {
|
When("invalid dependency", func() {
|
||||||
|
@ -236,7 +241,7 @@ var registryPushTests = Describe("push", func() {
|
||||||
args = []string{registryCmd, pushCmd, rulesRepo, rulesfiletgz, "--config", configFile, "--type", "rulesfile",
|
args = []string{registryCmd, pushCmd, rulesRepo, rulesfiletgz, "--config", configFile, "--type", "rulesfile",
|
||||||
"--version", "1.1.1", "--plain-http", "--depends-on", "wrongdep"}
|
"--version", "1.1.1", "--plain-http", "--depends-on", "wrongdep"}
|
||||||
})
|
})
|
||||||
pushAssertFailedBehavior(registryPushUsage, "ERRO: cannot parse \"wrongdep\": invalid artifact reference "+
|
pushAssertFailedBehavior(registryPushUsage, "ERROR cannot parse \"wrongdep\": invalid artifact reference "+
|
||||||
"(must be in the format \"name:version\")\n")
|
"(must be in the format \"name:version\")\n")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -244,8 +249,8 @@ var registryPushTests = Describe("push", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
args = []string{registryCmd, pushCmd, pluginsRepo, plugintgz, "--config", configFile, "--type", "plugin", "--version", "1.1.1", "--plain-http"}
|
args = []string{registryCmd, pushCmd, pluginsRepo, plugintgz, "--config", configFile, "--type", "plugin", "--version", "1.1.1", "--plain-http"}
|
||||||
})
|
})
|
||||||
pushAssertFailedBehavior(registryPushUsage, "ERRO: \"filepaths\" length (1) must match \"platforms\" "+
|
pushAssertFailedBehavior(registryPushUsage, "ERROR \"filepaths\" length (1) must match \"platforms\" "+
|
||||||
"length (0): number of filepaths and platform should be the same\n")
|
"length (0): number of filepaths and platform should be the same")
|
||||||
})
|
})
|
||||||
|
|
||||||
When("wrong plugin type", func() {
|
When("wrong plugin type", func() {
|
||||||
|
@ -253,306 +258,8 @@ var registryPushTests = Describe("push", func() {
|
||||||
args = []string{registryCmd, pushCmd, pluginsRepo, pluginsRepo, "--config", configFile,
|
args = []string{registryCmd, pushCmd, pluginsRepo, pluginsRepo, "--config", configFile,
|
||||||
"--type", "wrongType", "--version", "1.1.1", "--plain-http"}
|
"--type", "wrongType", "--version", "1.1.1", "--plain-http"}
|
||||||
})
|
})
|
||||||
pushAssertFailedBehavior(registryPushUsage, "ERRO: invalid argument \"wrongType\" for \"--type\" flag: must be one of \"rulesfile\", \"plugin\"\n")
|
pushAssertFailedBehavior(registryPushUsage, "ERROR invalid argument \"wrongType\" for \"--type\" "+
|
||||||
})
|
"flag: must be one of \"rulesfile\", \"plugin\", \"asset")
|
||||||
})
|
|
||||||
|
|
||||||
Context("success", func() {
|
|
||||||
const (
|
|
||||||
rulesRepoBaseName = "push-rulesfile"
|
|
||||||
pluginsRepoBaseName = "push-plugins"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
version = "1.1.1"
|
|
||||||
// registry/rulesRepoBaseName-randomInt
|
|
||||||
fullRepoName string
|
|
||||||
// rulesRepoBaseName-randomInt
|
|
||||||
repoName string
|
|
||||||
// It is set in the config layer.
|
|
||||||
artifactNameInConfigLayer = "test-rulesfile"
|
|
||||||
pushedTags = []string{"tag1", "tag2", "latest"}
|
|
||||||
|
|
||||||
// Variables passed as arguments to the push command. Each test case updates them
|
|
||||||
// to point to the file on disk living in pkg/test/data.
|
|
||||||
rulesfile string
|
|
||||||
plugin string
|
|
||||||
pluginRaw string
|
|
||||||
|
|
||||||
// Plugin's platforms.
|
|
||||||
platformARM64 = "linux/arm64"
|
|
||||||
platformAMD64 = "linux/amd64"
|
|
||||||
|
|
||||||
// Data fetched from registry and used for assertions.
|
|
||||||
pluginData *testutils.PluginArtifact
|
|
||||||
rulesfileData *testutils.RulesfileArtifact
|
|
||||||
)
|
|
||||||
|
|
||||||
// We keep it inside the success context since need the variables of this context.
|
|
||||||
var AssertSuccesBehaviour = func(dependencies, requirements, annotation bool) {
|
|
||||||
It("should succeed", func() {
|
|
||||||
// We do not check the error here since we are checking it before
|
|
||||||
// pulling the artifact.
|
|
||||||
By("checking no error in output")
|
|
||||||
Expect(output).ShouldNot(gbytes.Say("ERRO:"))
|
|
||||||
Expect(output).ShouldNot(gbytes.Say("Unable to remove temporary dir"))
|
|
||||||
|
|
||||||
By("checking descriptor")
|
|
||||||
Expect(rulesfileData.Descriptor.MediaType).Should(Equal(v1.MediaTypeImageManifest))
|
|
||||||
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(rulesfileData.Descriptor.Digest.String())))
|
|
||||||
|
|
||||||
By("checking manifest")
|
|
||||||
Expect(rulesfileData.Layer.Manifest.Layers).Should(HaveLen(1))
|
|
||||||
if annotation {
|
|
||||||
Expect(rulesfileData.Layer.Manifest.Annotations).Should(HaveKeyWithValue("org.opencontainers.image.source", anSource))
|
|
||||||
} else {
|
|
||||||
Expect(rulesfileData.Layer.Manifest.Annotations).ShouldNot(HaveKeyWithValue("org.opencontainers.image.source", anSource))
|
|
||||||
}
|
|
||||||
|
|
||||||
By("checking config layer")
|
|
||||||
Expect(rulesfileData.Layer.Config.Version).Should(Equal(version))
|
|
||||||
Expect(rulesfileData.Layer.Config.Name).Should(Equal(artifactNameInConfigLayer))
|
|
||||||
if dependencies {
|
|
||||||
Expect(fmt.Sprintf("%s:%s", rulesfileData.Layer.Config.Dependencies[0].Name,
|
|
||||||
rulesfileData.Layer.Config.Dependencies[0].Version)).Should(Equal(dep1))
|
|
||||||
Expect(fmt.Sprintf("%s:%s|%s:%s", rulesfileData.Layer.Config.Dependencies[1].Name,
|
|
||||||
rulesfileData.Layer.Config.Dependencies[1].Version, rulesfileData.Layer.Config.Dependencies[1].Alternatives[0].Name,
|
|
||||||
rulesfileData.Layer.Config.Dependencies[1].Alternatives[0].Version)).Should(Equal(dep2))
|
|
||||||
} else {
|
|
||||||
Expect(rulesfileData.Layer.Config.Dependencies).Should(HaveLen(0))
|
|
||||||
}
|
|
||||||
if requirements {
|
|
||||||
Expect(fmt.Sprintf("%s:%s", rulesfileData.Layer.Config.Requirements[0].Name,
|
|
||||||
rulesfileData.Layer.Config.Requirements[0].Version)).Should(Equal(req))
|
|
||||||
} else {
|
|
||||||
Expect(rulesfileData.Layer.Config.Requirements).Should(HaveLen(0))
|
|
||||||
}
|
|
||||||
|
|
||||||
By("checking tags")
|
|
||||||
Expect(rulesfileData.Tags).Should(HaveLen(len(pushedTags)))
|
|
||||||
Expect(rulesfileData.Tags).Should(ContainElements(pushedTags))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Here we are testing all the success cases for the push command. The artifact type used here is of type
|
|
||||||
// rulesfile. Keep in mind that here we are testing also the common flags that could be used by the plugin
|
|
||||||
// artifacts. So we are testing that common logic only once, and are doing it here.
|
|
||||||
commonFlagsAndRulesfileSpecificFlags := Context("rulesfiles and common flags", func() {
|
|
||||||
JustBeforeEach(func() {
|
|
||||||
// This runs after the push command, so check the returned error before proceeding.
|
|
||||||
Expect(err).ShouldNot(HaveOccurred())
|
|
||||||
rulesfileData, err = testutils.FetchRulesfileFromRegistry(ctx, repoName, pushedTags[0], orasRegistry)
|
|
||||||
Expect(err).ShouldNot(HaveOccurred())
|
|
||||||
})
|
|
||||||
|
|
||||||
JustAfterEach(func() {
|
|
||||||
// This variable could be changed by single tests.
|
|
||||||
// Make sure to set them at their default values.
|
|
||||||
artifactNameInConfigLayer = "test-rulesfile"
|
|
||||||
pushedTags = []string{"tag1", "tag2", "latest"}
|
|
||||||
})
|
|
||||||
|
|
||||||
BeforeEach(func() {
|
|
||||||
repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName)
|
|
||||||
})
|
|
||||||
When("with full flags and args", func() {
|
|
||||||
BeforeEach(func() {
|
|
||||||
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
|
|
||||||
"--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req, "--annotation-source", anSource,
|
|
||||||
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
|
|
||||||
})
|
|
||||||
AssertSuccesBehaviour(true, true, true)
|
|
||||||
})
|
|
||||||
|
|
||||||
When("no --name flag provided", func() {
|
|
||||||
BeforeEach(func() {
|
|
||||||
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
|
|
||||||
"--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req, "--annotation-source", anSource,
|
|
||||||
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2]}
|
|
||||||
// Set name to the expected one.
|
|
||||||
artifactNameInConfigLayer = repoName
|
|
||||||
})
|
|
||||||
AssertSuccesBehaviour(true, true, true)
|
|
||||||
})
|
|
||||||
|
|
||||||
When("no --annotation-source provided", func() {
|
|
||||||
BeforeEach(func() {
|
|
||||||
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
|
|
||||||
"--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req,
|
|
||||||
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
|
|
||||||
})
|
|
||||||
AssertSuccesBehaviour(true, true, false)
|
|
||||||
})
|
|
||||||
|
|
||||||
When("no --tags provided", func() {
|
|
||||||
BeforeEach(func() {
|
|
||||||
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
|
|
||||||
"--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req, "--annotation-source", anSource,
|
|
||||||
"--name", artifactNameInConfigLayer}
|
|
||||||
// We expect that latest tag is pushed, so set it in the pushed tags.
|
|
||||||
pushedTags = []string{"latest"}
|
|
||||||
})
|
|
||||||
AssertSuccesBehaviour(true, true, true)
|
|
||||||
})
|
|
||||||
|
|
||||||
When("no --depends-on flag provided", func() {
|
|
||||||
BeforeEach(func() {
|
|
||||||
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
|
|
||||||
"--plain-http", "--requires", req, "--annotation-source", anSource,
|
|
||||||
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
|
|
||||||
})
|
|
||||||
AssertSuccesBehaviour(false, true, true)
|
|
||||||
})
|
|
||||||
|
|
||||||
When("no --requires flag provided", func() {
|
|
||||||
BeforeEach(func() {
|
|
||||||
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
|
|
||||||
"--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--annotation-source", anSource,
|
|
||||||
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
|
|
||||||
})
|
|
||||||
AssertSuccesBehaviour(true, false, true)
|
|
||||||
})
|
|
||||||
|
|
||||||
When("only required flags", func() {
|
|
||||||
BeforeEach(func() {
|
|
||||||
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
|
|
||||||
"--plain-http"}
|
|
||||||
// Set name to the expected one.
|
|
||||||
artifactNameInConfigLayer = repoName
|
|
||||||
// We expect that latest tag is pushed, so set it in the pushed tags.
|
|
||||||
pushedTags = []string{"latest"}
|
|
||||||
})
|
|
||||||
AssertSuccesBehaviour(false, false, false)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Context("rulesfile", func() {
|
|
||||||
Context("tar.gz format", func() {
|
|
||||||
rulesfile = rulesfiletgz
|
|
||||||
var _ = commonFlagsAndRulesfileSpecificFlags
|
|
||||||
})
|
|
||||||
|
|
||||||
Context("raw format", func() {
|
|
||||||
rulesfile = rulesfileyaml
|
|
||||||
|
|
||||||
// Push a raw rulesfiles using all the flags combinations.
|
|
||||||
var _ = commonFlagsAndRulesfileSpecificFlags
|
|
||||||
|
|
||||||
Context("filesystem cleanup", func() {
|
|
||||||
// Push a raw rulesfile.
|
|
||||||
BeforeEach(func() {
|
|
||||||
// Some values such as fullRepoName is the last one set by the other tests or the default one.
|
|
||||||
// Anyway we do not really care since the tar.gz is created before.
|
|
||||||
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
|
|
||||||
"--plain-http"}
|
|
||||||
})
|
|
||||||
|
|
||||||
It("temp dir should not exist", func() {
|
|
||||||
Expect(err).ShouldNot(HaveOccurred())
|
|
||||||
entries, err := os.ReadDir("/tmp")
|
|
||||||
Expect(err).ShouldNot(HaveOccurred())
|
|
||||||
for _, e := range entries {
|
|
||||||
if e.IsDir() {
|
|
||||||
matched, err := filepath.Match(utils.TmpDirPrefix+"*", regexp.QuoteMeta(e.Name()))
|
|
||||||
Expect(err).ShouldNot(HaveOccurred())
|
|
||||||
Expect(matched).ShouldNot(BeTrue())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// We keep it inside the success context since need the variables of this context.
|
|
||||||
var AssertSuccessBehaviourPlugins = func(dependencies, requirements, annotation bool) {
|
|
||||||
It("should succeed", func() {
|
|
||||||
// We do not check the error here since we are checking it before
|
|
||||||
// pulling the artifact.
|
|
||||||
By("checking no error in output")
|
|
||||||
Expect(output).ShouldNot(gbytes.Say("ERRO:"))
|
|
||||||
Expect(output).ShouldNot(gbytes.Say("Unable to remove temporary dir"))
|
|
||||||
|
|
||||||
By("checking descriptor")
|
|
||||||
Expect(pluginData.Descriptor.MediaType).Should(Equal(v1.MediaTypeImageIndex))
|
|
||||||
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(pluginData.Descriptor.Digest.String())))
|
|
||||||
|
|
||||||
By("checking index")
|
|
||||||
Expect(pluginData.Index.Manifests).Should(HaveLen(2))
|
|
||||||
|
|
||||||
if annotation {
|
|
||||||
Expect(pluginData.Index.Annotations).Should(HaveKeyWithValue("org.opencontainers.image.source", anSource))
|
|
||||||
} else {
|
|
||||||
Expect(pluginData.Descriptor.Annotations).ShouldNot(HaveKeyWithValue("org.opencontainers.image.source", anSource))
|
|
||||||
}
|
|
||||||
|
|
||||||
By("checking platforms")
|
|
||||||
Expect(pluginData.Platforms).Should(HaveKey(platformARM64))
|
|
||||||
Expect(pluginData.Platforms).Should(HaveKey(platformAMD64))
|
|
||||||
|
|
||||||
By("checking config layer")
|
|
||||||
for _, p := range pluginData.Platforms {
|
|
||||||
Expect(p.Config.Version).Should(Equal(version))
|
|
||||||
Expect(p.Config.Name).Should(Equal(artifactNameInConfigLayer))
|
|
||||||
if dependencies {
|
|
||||||
Expect(fmt.Sprintf("%s:%s", p.Config.Dependencies[0].Name, p.Config.Dependencies[0].Version)).Should(Equal(dep1))
|
|
||||||
Expect(fmt.Sprintf("%s:%s|%s:%s", p.Config.Dependencies[1].Name, p.Config.Dependencies[1].Version,
|
|
||||||
p.Config.Dependencies[1].Alternatives[0].Name, p.Config.Dependencies[1].Alternatives[0].Version)).Should(Equal(dep2))
|
|
||||||
} else {
|
|
||||||
Expect(p.Config.Dependencies).Should(HaveLen(0))
|
|
||||||
}
|
|
||||||
if requirements {
|
|
||||||
Expect(fmt.Sprintf("%s:%s", p.Config.Requirements[0].Name, p.Config.Requirements[0].Version)).Should(Equal(req))
|
|
||||||
} else {
|
|
||||||
Expect(p.Config.Requirements).Should(HaveLen(0))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
By("checking tags")
|
|
||||||
Expect(pluginData.Tags).Should(HaveLen(len(pushedTags)))
|
|
||||||
Expect(pluginData.Tags).Should(ContainElements(pushedTags))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Here we are testing the success cases for the push command using a plugin artifact and its related flags.
|
|
||||||
// Other flags related to the plugin artifacts are tested in the rulesfile artifact section.
|
|
||||||
PluginsSpecificFlags := Context("plugins specific flags", func() {
|
|
||||||
JustBeforeEach(func() {
|
|
||||||
// This runs after the push command, so check the returned error before proceeding.
|
|
||||||
Expect(err).ShouldNot(HaveOccurred())
|
|
||||||
pluginData, err = testutils.FetchPluginFromRegistry(ctx, repoName, pushedTags[0], orasRegistry)
|
|
||||||
Expect(err).ShouldNot(HaveOccurred())
|
|
||||||
})
|
|
||||||
|
|
||||||
JustAfterEach(func() {
|
|
||||||
// This variable could be changed by single tests.
|
|
||||||
// Make sure to set them at their default values.
|
|
||||||
artifactNameInConfigLayer = "test-plugin"
|
|
||||||
pushedTags = []string{"tag1", "tag2", "latest"}
|
|
||||||
})
|
|
||||||
|
|
||||||
BeforeEach(func() {
|
|
||||||
repoName, fullRepoName = randomRulesRepoName(registry, pluginsRepoBaseName)
|
|
||||||
})
|
|
||||||
When("with full flags and args", func() {
|
|
||||||
BeforeEach(func() {
|
|
||||||
args = []string{registryCmd, pushCmd, fullRepoName, plugin, pluginRaw, "--type", "plugin", "--platform",
|
|
||||||
platformAMD64, "--platform", platformARM64, "--version", version, "--config", configFile,
|
|
||||||
"--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req, "--annotation-source", anSource,
|
|
||||||
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
|
|
||||||
})
|
|
||||||
AssertSuccessBehaviourPlugins(true, true, true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Context("plugin", func() {
|
|
||||||
Context("tar.gz + raw format format", func() {
|
|
||||||
plugin = plugintgz
|
|
||||||
// We do not really care what the file is.
|
|
||||||
pluginRaw = rulesfileyaml
|
|
||||||
var _ = PluginsSpecificFlags
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2022 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -33,8 +34,9 @@ func NewRegistryCmd(ctx context.Context, opt *commonoptions.Common) *cobra.Comma
|
||||||
DisableFlagsInUseLine: true,
|
DisableFlagsInUseLine: true,
|
||||||
Short: "Interact with OCI registries",
|
Short: "Interact with OCI registries",
|
||||||
Long: "Interact with OCI registries",
|
Long: "Interact with OCI registries",
|
||||||
SilenceErrors: true,
|
|
||||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
// Initialize the options.
|
||||||
|
opt.Initialize()
|
||||||
// Load configuration from ENV variables and/or config file.
|
// Load configuration from ENV variables and/or config file.
|
||||||
return config.Load(opt.ConfigFile)
|
return config.Load(opt.ConfigFile)
|
||||||
},
|
},
|
||||||
|
|
19
cmd/root.go
19
cmd/root.go
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2022 The Falco Authors
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -20,12 +21,12 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/falcosecurity/falcoctl/cmd/artifact"
|
"github.com/falcosecurity/falcoctl/cmd/artifact"
|
||||||
|
"github.com/falcosecurity/falcoctl/cmd/driver"
|
||||||
"github.com/falcosecurity/falcoctl/cmd/index"
|
"github.com/falcosecurity/falcoctl/cmd/index"
|
||||||
"github.com/falcosecurity/falcoctl/cmd/registry"
|
"github.com/falcosecurity/falcoctl/cmd/registry"
|
||||||
"github.com/falcosecurity/falcoctl/cmd/tls"
|
"github.com/falcosecurity/falcoctl/cmd/tls"
|
||||||
"github.com/falcosecurity/falcoctl/cmd/version"
|
"github.com/falcosecurity/falcoctl/cmd/version"
|
||||||
"github.com/falcosecurity/falcoctl/pkg/options"
|
"github.com/falcosecurity/falcoctl/pkg/options"
|
||||||
"github.com/falcosecurity/falcoctl/pkg/output"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -47,7 +48,16 @@ func New(ctx context.Context, opt *options.Common) *cobra.Command {
|
||||||
Use: "falcoctl",
|
Use: "falcoctl",
|
||||||
Short: "The official CLI tool for working with Falco and its ecosystem components",
|
Short: "The official CLI tool for working with Falco and its ecosystem components",
|
||||||
Long: longRootCmd,
|
Long: longRootCmd,
|
||||||
|
SilenceErrors: true,
|
||||||
|
SilenceUsage: true,
|
||||||
|
TraverseChildren: true,
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
|
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||||
|
// Initialize the common options for all subcommands.
|
||||||
|
// Subcommands con overwrite the default settings by calling initialize with
|
||||||
|
// different options.
|
||||||
|
opt.Initialize()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Global flags
|
// Global flags
|
||||||
|
@ -59,15 +69,16 @@ func New(ctx context.Context, opt *options.Common) *cobra.Command {
|
||||||
rootCmd.AddCommand(registry.NewRegistryCmd(ctx, opt))
|
rootCmd.AddCommand(registry.NewRegistryCmd(ctx, opt))
|
||||||
rootCmd.AddCommand(index.NewIndexCmd(ctx, opt))
|
rootCmd.AddCommand(index.NewIndexCmd(ctx, opt))
|
||||||
rootCmd.AddCommand(artifact.NewArtifactCmd(ctx, opt))
|
rootCmd.AddCommand(artifact.NewArtifactCmd(ctx, opt))
|
||||||
|
rootCmd.AddCommand(driver.NewDriverCmd(ctx, opt))
|
||||||
|
|
||||||
return rootCmd
|
return rootCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute configures the signal handlers and runs the command.
|
// Execute configures the signal handlers and runs the command.
|
||||||
func Execute(cmd *cobra.Command, printer *output.Printer) error {
|
func Execute(cmd *cobra.Command, opt *options.Common) error {
|
||||||
// we do not log the error here since we expect that each subcommand
|
// we do not log the error here since we expect that each subcommand
|
||||||
// handles the errors by itself.
|
// handles the errors by itself.
|
||||||
err := cmd.Execute()
|
err := cmd.Execute()
|
||||||
printer.CheckErr(err)
|
opt.Printer.CheckErr(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,175 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (C) 2023 The Falco Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package cmd_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/onsi/gomega/gbytes"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/falcosecurity/falcoctl/cmd"
|
||||||
|
commonoptions "github.com/falcosecurity/falcoctl/pkg/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
var usageLinux = `
|
||||||
|
__ _ _ _
|
||||||
|
/ _| __ _| | ___ ___ ___| |_| |
|
||||||
|
| |_ / _ | |/ __/ _ \ / __| __| |
|
||||||
|
| _| (_| | | (_| (_) | (__| |_| |
|
||||||
|
|_| \__,_|_|\___\___/ \___|\__|_|
|
||||||
|
|
||||||
|
|
||||||
|
The official CLI tool for working with Falco and its ecosystem components
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
falcoctl [command]
|
||||||
|
|
||||||
|
Available Commands:
|
||||||
|
artifact Interact with Falco artifacts
|
||||||
|
completion Generate the autocompletion script for the specified shell
|
||||||
|
driver Interact with falcosecurity driver
|
||||||
|
help Help about any command
|
||||||
|
index Interact with index
|
||||||
|
registry Interact with OCI registries
|
||||||
|
tls Generate and install TLS material for Falco
|
||||||
|
version Print the falcoctl version information
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml")
|
||||||
|
-h, --help help for falcoctl
|
||||||
|
--log-format string Set formatting for logs (color, text, json) (default "color")
|
||||||
|
--log-level string Set level for logs (info, warn, debug, trace) (default "info")
|
||||||
|
|
||||||
|
Use "falcoctl [command] --help" for more information about a command.
|
||||||
|
`
|
||||||
|
|
||||||
|
var usageOthers = `
|
||||||
|
__ _ _ _
|
||||||
|
/ _| __ _| | ___ ___ ___| |_| |
|
||||||
|
| |_ / _ | |/ __/ _ \ / __| __| |
|
||||||
|
| _| (_| | | (_| (_) | (__| |_| |
|
||||||
|
|_| \__,_|_|\___\___/ \___|\__|_|
|
||||||
|
|
||||||
|
|
||||||
|
The official CLI tool for working with Falco and its ecosystem components
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
falcoctl [command]
|
||||||
|
|
||||||
|
Available Commands:
|
||||||
|
artifact Interact with Falco artifacts
|
||||||
|
completion Generate the autocompletion script for the specified shell
|
||||||
|
help Help about any command
|
||||||
|
index Interact with index
|
||||||
|
registry Interact with OCI registries
|
||||||
|
tls Generate and install TLS material for Falco
|
||||||
|
version Print the falcoctl version information
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml")
|
||||||
|
-h, --help help for falcoctl
|
||||||
|
--log-format string Set formatting for logs (color, text, json) (default "color")
|
||||||
|
--log-level string Set level for logs (info, warn, debug, trace) (default "info")
|
||||||
|
|
||||||
|
Use "falcoctl [command] --help" for more information about a command.
|
||||||
|
`
|
||||||
|
|
||||||
|
func getUsage() string {
|
||||||
|
if runtime.GOOS == "linux" {
|
||||||
|
return usageLinux
|
||||||
|
}
|
||||||
|
return usageOthers
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Describe("Root", func() {
|
||||||
|
var (
|
||||||
|
rootCmd *cobra.Command
|
||||||
|
ctx = context.Background()
|
||||||
|
opt = commonoptions.NewOptions()
|
||||||
|
err error
|
||||||
|
outputBuf = gbytes.NewBuffer()
|
||||||
|
args []string
|
||||||
|
)
|
||||||
|
|
||||||
|
JustBeforeEach(func() {
|
||||||
|
// Each test creates a new root command, configures, and executes it.
|
||||||
|
opt.Initialize(commonoptions.WithWriter(outputBuf))
|
||||||
|
rootCmd = cmd.New(ctx, opt)
|
||||||
|
rootCmd.SetOut(outputBuf)
|
||||||
|
rootCmd.SetErr(outputBuf)
|
||||||
|
rootCmd.SetArgs(args)
|
||||||
|
err = cmd.Execute(rootCmd, opt)
|
||||||
|
})
|
||||||
|
|
||||||
|
JustAfterEach(func() {
|
||||||
|
// Reset the output buffer.
|
||||||
|
Expect(outputBuf.Clear()).ShouldNot(HaveOccurred())
|
||||||
|
// Reset the arguments
|
||||||
|
args = nil
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("Without args and without flags", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
// Set args to an empty slice.
|
||||||
|
args = []string{}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Should print the usage message", func() {
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
Expect(string(outputBuf.Contents())).Should(Equal(getUsage()))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("help argument", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
// Set the help argument.
|
||||||
|
args = []string{"help"}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Should print the usage message", func() {
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
Expect(string(outputBuf.Contents())).Should(Equal(getUsage()))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("help flag", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
// Set the help argument.
|
||||||
|
args = []string{"--help"}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Should print the usage message", func() {
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
Expect(string(outputBuf.Contents())).Should(Equal(getUsage()))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("wrong flag", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
// Set the help argument.
|
||||||
|
args = []string{"--wrong-flag"}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Should error and print the error", func() {
|
||||||
|
Expect(err).Should(HaveOccurred())
|
||||||
|
Expect(outputBuf).Should(gbytes.Say("ERROR unknown flag: --wrong-flag"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,29 +0,0 @@
|
||||||
|
|
||||||
__ _ _ _
|
|
||||||
/ _| __ _| | ___ ___ ___| |_| |
|
|
||||||
| |_ / _ | |/ __/ _ \ / __| __| |
|
|
||||||
| _| (_| | | (_| (_) | (__| |_| |
|
|
||||||
|_| \__,_|_|\___\___/ \___|\__|_|
|
|
||||||
|
|
||||||
|
|
||||||
The official CLI tool for working with Falco and its ecosystem components
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
falcoctl [command]
|
|
||||||
|
|
||||||
Available Commands:
|
|
||||||
artifact Interact with Falco artifacts
|
|
||||||
completion Generate the autocompletion script for the specified shell
|
|
||||||
help Help about any command
|
|
||||||
index Interact with index
|
|
||||||
registry Interact with OCI registries
|
|
||||||
tls Generate and install TLS material for Falco
|
|
||||||
version Print the falcoctl version information
|
|
||||||
|
|
||||||
Flags:
|
|
||||||
--config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml")
|
|
||||||
--disable-styling Disable output styling such as spinners, progress bars and colors. Styling is automatically disabled if not attacched to a tty (default false)
|
|
||||||
-h, --help help for falcoctl
|
|
||||||
-v, --verbose Enable verbose logs (default false)
|
|
||||||
|
|
||||||
Use "falcoctl [command] --help" for more information about a command.
|
|
|
@ -1,29 +0,0 @@
|
||||||
|
|
||||||
__ _ _ _
|
|
||||||
/ _| __ _| | ___ ___ ___| |_| |
|
|
||||||
| |_ / _ | |/ __/ _ \ / __| __| |
|
|
||||||
| _| (_| | | (_| (_) | (__| |_| |
|
|
||||||
|_| \__,_|_|\___\___/ \___|\__|_|
|
|
||||||
|
|
||||||
|
|
||||||
The official CLI tool for working with Falco and its ecosystem components
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
falcoctl [command]
|
|
||||||
|
|
||||||
Available Commands:
|
|
||||||
artifact Interact with Falco artifacts
|
|
||||||
completion Generate the autocompletion script for the specified shell
|
|
||||||
help Help about any command
|
|
||||||
index Interact with index
|
|
||||||
registry Interact with OCI registries
|
|
||||||
tls Generate and install TLS material for Falco
|
|
||||||
version Print the falcoctl version information
|
|
||||||
|
|
||||||
Flags:
|
|
||||||
--config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml")
|
|
||||||
--disable-styling Disable output styling such as spinners, progress bars and colors. Styling is automatically disabled if not attacched to a tty (default false)
|
|
||||||
-h, --help help for falcoctl
|
|
||||||
-v, --verbose Enable verbose logs (default false)
|
|
||||||
|
|
||||||
Use "falcoctl [command] --help" for more information about a command.
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue