Skip to content

Commit 8bc7bbe

Browse files
Merge pull request #666 from linode/dev
v5.54.0
2 parents 7991606 + afdc3b2 commit 8bc7bbe

39 files changed

+910
-176
lines changed

.github/workflows/docker-build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ jobs:
1717
- name: Build the Docker image
1818
run: docker build . --file Dockerfile --tag linode/cli:$(date +%s) --build-arg="github_token=$GITHUB_TOKEN"
1919
env:
20-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
20+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/e2e-suite-windows.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,60 @@ jobs:
103103
conclusion: process.env.conclusion
104104
});
105105
return result;
106+
107+
apply-calico-rules:
108+
runs-on: ubuntu-latest
109+
needs: [integration-fork-windows]
110+
if: ${{ success() || failure() }}
111+
112+
steps:
113+
- name: Checkout code
114+
uses: actions/checkout@v4
115+
with:
116+
fetch-depth: 0
117+
submodules: 'recursive'
118+
119+
- name: Download kubectl and calicoctl for LKE clusters
120+
run: |
121+
curl -LO "https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl"
122+
curl -LO "https://github.com/projectcalico/calico/releases/download/v3.25.0/calicoctl-linux-amd64"
123+
chmod +x calicoctl-linux-amd64 kubectl
124+
mv calicoctl-linux-amd64 /usr/local/bin/calicoctl
125+
mv kubectl /usr/local/bin/kubectl
126+
127+
- name: Apply Calico Rules to LKE
128+
run: |
129+
cd e2e_scripts/cloud_security_scripts/lke_calico_rules/ && ./lke_calico_rules_e2e.sh
130+
env:
131+
LINODE_TOKEN: ${{ secrets.LINODE_TOKEN_2 }}
132+
133+
add-fw-to-remaining-instances:
134+
runs-on: ubuntu-latest
135+
needs: [integration-fork-windows]
136+
if: ${{ success() || failure() }}
137+
138+
steps:
139+
- name: Set up Python
140+
uses: actions/setup-python@v5
141+
with:
142+
python-version: '3.x'
143+
144+
- name: Install Linode CLI
145+
run: |
146+
pip install linode-cli
147+
148+
- name: Create Firewall and Attach to Instances
149+
run: |
150+
FIREWALL_ID=$(linode-cli firewalls create --label "e2e-fw-$(date +%s)" --rules.inbound_policy "DROP" --rules.outbound_policy "ACCEPT" --text --format=id --no-headers)
151+
echo "Created Firewall with ID: $FIREWALL_ID"
152+
153+
for instance_id in $(linode-cli linodes list --format "id" --text --no-header); do
154+
echo "Attaching firewall to instance: $instance_id"
155+
if linode-cli firewalls device-create "$FIREWALL_ID" --id "$instance_id" --type linode; then
156+
echo "Firewall attached to instance $instance_id successfully."
157+
else
158+
echo "An error occurred while attaching firewall to instance $instance_id. Skipping..."
159+
fi
160+
done
161+
env:
162+
LINODE_CLI_TOKEN: ${{ secrets.LINODE_TOKEN_2 }}

.github/workflows/e2e-suite.yml

Lines changed: 70 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ on:
77
description: 'Use minimal test account'
88
required: false
99
default: 'false'
10-
test_path:
11-
description: "The path from 'test/integration' to the target to be tested, e.g. 'cli'"
10+
module:
11+
description: "The module from 'test/integration' to the target to be tested, e.g. 'cli, domains, events, etc'"
1212
required: false
1313
sha:
1414
description: 'The hash value of the commit.'
15-
required: false
15+
required: true
1616
default: ''
1717
pull_request_number:
1818
description: 'The number of the PR. Ensure sha value is provided'
@@ -28,15 +28,6 @@ jobs:
2828
runs-on: ubuntu-latest
2929
if: github.event_name == 'workflow_dispatch' && inputs.sha != '' || github.event_name == 'push' || github.event_name == 'pull_request'
3030
steps:
31-
- name: Validate Test Path
32-
uses: actions-ecosystem/action-regex-match@v2
33-
id: validate-tests
34-
if: ${{ inputs.test_path != '' }}
35-
with:
36-
text: ${{ inputs.test_path }}
37-
regex: '[^a-z0-9-:.\/_]' # Tests validation
38-
flags: gi
39-
4031
- name: Checkout Repository with SHA
4132
if: ${{ inputs.sha != '' }}
4233
uses: actions/checkout@v4
@@ -91,14 +82,6 @@ jobs:
9182
pip install certifi -U && \
9283
pip install .[obj,dev]
9384
94-
- name: Download kubectl and calicoctl for LKE clusters
95-
run: |
96-
curl -LO "https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl"
97-
curl -LO "https://github.com/projectcalico/calico/releases/download/v3.25.0/calicoctl-linux-amd64"
98-
chmod +x calicoctl-linux-amd64 kubectl
99-
mv calicoctl-linux-amd64 /usr/local/bin/calicoctl
100-
mv kubectl /usr/local/bin/kubectl
101-
10285
- name: Install Package
10386
run: make install
10487
env:
@@ -112,18 +95,10 @@ jobs:
11295
run: |
11396
timestamp=$(date +'%Y%m%d%H%M')
11497
report_filename="${timestamp}_cli_test_report.xml"
115-
make testint TEST_ARGS="--junitxml=${report_filename}"
116-
if: ${{ steps.validate-tests.outputs.match == '' || inputs.test_path == '' }}
98+
make testint TEST_ARGS="--junitxml=${report_filename}" MODULE="${{ inputs.module }}"
11799
env:
118100
LINODE_CLI_TOKEN: ${{ env.LINODE_CLI_TOKEN }}
119101

120-
- name: Apply Calico Rules to LKE
121-
if: always()
122-
run: |
123-
cd scripts && ./lke_calico_rules_e2e.sh
124-
env:
125-
LINODE_TOKEN: ${{ env.LINODE_CLI_TOKEN }}
126-
127102
- name: Upload test results
128103
if: always()
129104
run: |
@@ -168,10 +143,75 @@ jobs:
168143
});
169144
return result;
170145
146+
apply-calico-rules:
147+
runs-on: ubuntu-latest
148+
needs: [integration_tests]
149+
if: ${{ success() || failure() }}
150+
151+
steps:
152+
- name: Checkout code
153+
uses: actions/checkout@v4
154+
with:
155+
fetch-depth: 0
156+
submodules: 'recursive'
157+
158+
- name: Set LINODE_CLI_TOKEN
159+
run: |
160+
echo "LINODE_CLI_TOKEN=${{ secrets[inputs.use_minimal_test_account == 'true' && 'MINIMAL_LINODE_TOKEN' || 'LINODE_TOKEN'] }}" >> $GITHUB_ENV
161+
162+
- name: Download kubectl and calicoctl for LKE clusters
163+
run: |
164+
curl -LO "https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl"
165+
curl -LO "https://github.com/projectcalico/calico/releases/download/v3.25.0/calicoctl-linux-amd64"
166+
chmod +x calicoctl-linux-amd64 kubectl
167+
mv calicoctl-linux-amd64 /usr/local/bin/calicoctl
168+
mv kubectl /usr/local/bin/kubectl
169+
170+
- name: Apply Calico Rules to LKE
171+
run: |
172+
cd e2e_scripts/cloud_security_scripts/lke_calico_rules/ && ./lke_calico_rules_e2e.sh
173+
env:
174+
LINODE_TOKEN: ${{ env.LINODE_CLI_TOKEN }}
175+
176+
add-fw-to-remaining-instances:
177+
runs-on: ubuntu-latest
178+
needs: [integration_tests]
179+
if: ${{ success() || failure() }}
180+
181+
steps:
182+
- name: Set up Python
183+
uses: actions/setup-python@v5
184+
with:
185+
python-version: '3.x'
186+
187+
- name: Install Linode CLI
188+
run: |
189+
pip install linode-cli
190+
191+
- name: Set LINODE_CLI_TOKEN
192+
run: |
193+
echo "LINODE_CLI_TOKEN=${{ secrets[inputs.use_minimal_test_account == 'true' && 'MINIMAL_LINODE_TOKEN' || 'LINODE_TOKEN'] }}" >> $GITHUB_ENV
194+
195+
- name: Create Firewall and Attach to Instances
196+
run: |
197+
FIREWALL_ID=$(linode-cli firewalls create --label "e2e-fw-$(date +%s)" --rules.inbound_policy "DROP" --rules.outbound_policy "ACCEPT" --text --format=id --no-headers)
198+
echo "Created Firewall with ID: $FIREWALL_ID"
199+
200+
for instance_id in $(linode-cli linodes list --format "id" --text --no-header); do
201+
echo "Attaching firewall to instance: $instance_id"
202+
if linode-cli firewalls device-create "$FIREWALL_ID" --id "$instance_id" --type linode; then
203+
echo "Firewall attached to instance $instance_id successfully."
204+
else
205+
echo "An error occurred while attaching firewall to instance $instance_id. Skipping..."
206+
fi
207+
done
208+
env:
209+
LINODE_CLI_TOKEN: ${{ env.LINODE_CLI_TOKEN }}
210+
171211
notify-slack:
172212
runs-on: ubuntu-latest
173213
needs: [integration_tests]
174-
if: always() && github.repository == 'linode/linode-cli' # Run even if integration tests fail and only on main repository
214+
if: ${{ (success() || failure()) && github.repository == 'linode/linode-cli' }} # Run even if integration tests fail and only on main repository
175215

176216
steps:
177217
- name: Notify Slack
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Notify Dev DX Channel on Release
2+
on:
3+
release:
4+
types: [published]
5+
workflow_dispatch: null
6+
7+
jobs:
8+
notify:
9+
if: github.repository == 'linode/linode-cli'
10+
runs-on: ubuntu-latest
11+
steps:
12+
- name: Notify Slack - Main Message
13+
id: main_message
14+
uses: slackapi/[email protected]
15+
with:
16+
channel-id: ${{ secrets.CLI_SLACK_CHANNEL_ID }}
17+
payload: |
18+
{
19+
"blocks": [
20+
{
21+
"type": "section",
22+
"text": {
23+
"type": "mrkdwn",
24+
"text": "*New Release Published: _linode-cli_ <${{ github.event.release.html_url }}|${{ github.event.release.tag_name }}> is now live!* :tada:"
25+
}
26+
}
27+
]
28+
}
29+
env:
30+
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}

Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
FROM python:3.11-slim AS builder
22

33
ARG linode_cli_version
4+
45
ARG github_token
56

67
WORKDIR /src

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#
22
# Makefile for more convenient building of the Linode CLI and its baked content
33
#
4+
5+
# Test-related arguments
46
MODULE :=
57
TEST_CASE_COMMAND :=
68
TEST_ARGS :=
@@ -9,7 +11,6 @@ ifdef TEST_CASE
911
TEST_CASE_COMMAND = -k $(TEST_CASE)
1012
endif
1113

12-
1314
SPEC_VERSION ?= latest
1415
ifndef SPEC
1516
override SPEC = $(shell ./resolve_spec_url ${SPEC_VERSION})

linodecli/__init__.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,7 @@
1515
from linodecli import plugins
1616
from linodecli.exit_codes import ExitCodes
1717

18-
from .arg_helpers import (
19-
bake_command,
20-
register_args,
21-
register_plugin,
22-
remove_plugin,
23-
)
18+
from .arg_helpers import register_args, register_plugin, remove_plugin
2419
from .cli import CLI
2520
from .completion import get_completions
2621
from .configuration import ENV_TOKEN_NAME
@@ -101,25 +96,28 @@ def main(): # pylint: disable=too-many-branches,too-many-statements
10196
# handle a bake - this is used to parse a spec and bake it as a pickle
10297
if parsed.command == "bake":
10398
if parsed.action is None:
104-
print("No spec provided, cannot bake")
99+
print("No spec provided, cannot bake", file=sys.stderr)
105100
sys.exit(ExitCodes.ARGUMENT_ERROR)
106-
bake_command(cli, parsed.action)
101+
cli.bake(parsed.action)
107102
sys.exit(ExitCodes.SUCCESS)
108103
elif cli.ops is None:
109104
# if not spec was found and we weren't baking, we're doomed
110105
sys.exit(ExitCodes.ARGUMENT_ERROR)
111106

112107
if parsed.command == "register-plugin":
113108
if parsed.action is None:
114-
print("register-plugin requires a module name!")
109+
print("register-plugin requires a module name!", file=sys.stderr)
115110
sys.exit(ExitCodes.ARGUMENT_ERROR)
116111
msg, code = register_plugin(parsed.action, cli.config, cli.ops)
117112
print(msg)
118113
sys.exit(code)
119114

120115
if parsed.command == "remove-plugin":
121116
if parsed.action is None:
122-
print("remove-plugin requires a plugin name to remove!")
117+
print(
118+
"remove-plugin requires a plugin name to remove!",
119+
file=sys.stderr,
120+
)
123121
sys.exit(ExitCodes.ARGUMENT_ERROR)
124122
msg, code = remove_plugin(parsed.action, cli.config)
125123
print(msg)
@@ -216,7 +214,7 @@ def main(): # pylint: disable=too-many-branches,too-many-statements
216214
and parsed.command not in plugins.available(cli.config)
217215
and parsed.command not in HELP_TOPICS
218216
):
219-
print(f"Unrecognized command {parsed.command}")
217+
print(f"Unrecognized command {parsed.command}", file=sys.stderr)
220218
sys.exit(ExitCodes.UNRECOGNIZED_COMMAND)
221219

222220
# handle a help for a command - either --help or no action triggers this

linodecli/api_request.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,9 @@ def _print_request_debug_info(method, url, headers, body):
283283
"""
284284
print(f"> {method.__name__.upper()} {url}", file=sys.stderr)
285285
for k, v in headers.items():
286+
# If this is the Authorization header, sanitize the token
287+
if k.lower() == "authorization":
288+
v = "Bearer " + "*" * 64
286289
print(f"> {k}: {v}", file=sys.stderr)
287290
print("> Body:", file=sys.stderr)
288291
print("> ", body or "", file=sys.stderr)

linodecli/arg_helpers.py

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,10 @@
22
"""
33
Argument parser for the linode CLI
44
"""
5-
6-
import os
75
import sys
86
from importlib import import_module
97

10-
import requests
11-
import yaml
12-
138
from linodecli import plugins
14-
from linodecli.exit_codes import ExitCodes
159
from linodecli.helpers import (
1610
register_args_shared,
1711
register_debug_arg,
@@ -107,7 +101,10 @@ def register_plugin(module, config, ops):
107101

108102
reregistering = False
109103
if plugin_name in plugins.available(config):
110-
print(f"WARNING: Plugin {plugin_name} is already registered.\n\n")
104+
print(
105+
f"WARNING: Plugin {plugin_name} is already registered.\n\n",
106+
file=sys.stderr,
107+
)
111108
answer = input(f"Allow re-registration of {plugin_name}? [y/N] ")
112109
if not answer or answer not in "yY":
113110
return "Registration aborted.", 0
@@ -166,24 +163,3 @@ def remove_plugin(plugin_name, config):
166163

167164
config.write_config()
168165
return f"Plugin {plugin_name} removed", 0
169-
170-
171-
def bake_command(cli, spec_loc):
172-
"""
173-
Handle a bake command from args
174-
"""
175-
try:
176-
if os.path.exists(os.path.expanduser(spec_loc)):
177-
with open(os.path.expanduser(spec_loc), encoding="utf-8") as f:
178-
spec = yaml.safe_load(f.read())
179-
else: # try to GET it
180-
resp = requests.get(spec_loc, timeout=120)
181-
if resp.status_code == 200:
182-
spec = yaml.safe_load(resp.content)
183-
else:
184-
raise RuntimeError(f"Request failed to {spec_loc}")
185-
except Exception as e:
186-
print(f"Could not load spec: {e}")
187-
sys.exit(ExitCodes.REQUEST_FAILED)
188-
189-
cli.bake(spec)

0 commit comments

Comments
 (0)