diff --git a/.github/workflows/go-test-multiplatform.yml b/.github/workflows/go-test-multiplatform.yml index d0287988..1f81b5f6 100644 --- a/.github/workflows/go-test-multiplatform.yml +++ b/.github/workflows/go-test-multiplatform.yml @@ -1,6 +1,7 @@ # # This GitHub action runs Packer go tests across # multiple runners. +# only test results from ubuntu-latest environment will be posted to test obj storage bucket after GHA execution # name: "Go Test Multi-Platform" @@ -27,6 +28,25 @@ jobs: run: | echo "Found Go $(cat .go-version)" echo "go-version=$(cat .go-version)" >> $GITHUB_OUTPUT + get-test-dependencies: + runs-on: ubuntu-latest + steps: + - name: Install system deps + run: sudo apt-get install -y build-essential + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: '3.x' + + - name: Install Python deps + run: pip3 install requests wheel boto3 + + - name: Set release version env + run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV + + - name: Install go-junit-report + run: go install github.com/jstemmer/go-junit-report/v2@latest darwin-go-tests: needs: @@ -40,7 +60,9 @@ jobs: go-version: ${{ needs.get-go-version.outputs.go-version }} - run: | echo "Testing with Go ${{ needs.get-go-version.outputs.go-version }}" - go test -race -count 1 ./... -timeout=3m + filename=$(ls | grep -E '^[0-9]{12}_packer_test_report\.json') + + go test -race -count 1 ./... -timeout=3m -v windows-go-tests: needs: @@ -54,11 +76,14 @@ jobs: go-version: ${{ needs.get-go-version.outputs.go-version }} - run: | echo "Testing with Go ${{ needs.get-go-version.outputs.go-version }}" - go test -race -count 1 ./... -timeout=3m + filename=$(ls | grep -E '^[0-9]{12}_packer_test_report\.json') + + go test -race -count 1 ./... -timeout=3m -v linux-go-tests: needs: - get-go-version + - get-test-dependencies runs-on: ubuntu-latest name: Linux Go tests steps: @@ -68,4 +93,40 @@ jobs: go-version: ${{ needs.get-go-version.outputs.go-version }} - run: | echo "Testing with Go ${{ needs.get-go-version.outputs.go-version }}" - go test -race -count 1 ./... -timeout=3m + filename=$(ls | grep -E '^[0-9]{12}_packer_test_report\.json') + + go test -race -count 1 ./... -timeout=3m -v -json | tee "$filename" + + - name: Convert JSON Report to XML + run: | + filename=$(ls | grep -E '^[0-9]{12}_packer_test_report\.json') + + if [ -f "$filename" ]; then + go_junit_report_dir=$(go env GOPATH)/bin + export PATH="$PATH:$go_junit_report_dir" + xml_filename=$(echo "$filename" | sed 's/\.json$/.xml/') + go-junit-report < "$filename" > "$xml_filename" + echo "Conversion from JSON to XML completed successfully." + else + echo "JSON test report file not found." + exit 1 + fi + env: + GO111MODULE: on + + - name: Add additional information to XML report + run: | + filename=$(ls | grep -E '^[0-9]{12}_packer_test_report\.xml$') + python scripts/add_to_xml_test_report.py \ + --branch_name "${{ env.RELEASE_VERSION }}" \ + --gha_run_id "$GITHUB_RUN_ID" \ + --gha_run_number "$GITHUB_RUN_NUMBER" \ + --xmlfile "${filename}" + + - name: Upload test results to bucket + env: + LINODE_CLI_OBJ_ACCESS_KEY: ${{ secrets.LINODE_CLI_OBJ_ACCESS_KEY }} + LINODE_CLI_OBJ_SECRET_KEY: ${{ secrets.LINODE_CLI_OBJ_SECRET_KEY }} + run: | + report_filename=$(ls | grep -E '^[0-9]{12}_packer_test_report\.xml$') + python3 scripts/test_report_upload_script.py "${report_filename}" \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b47b5c07..cca7512f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -34,12 +34,12 @@ jobs: run: echo "api_version=$(go run . describe | jq -r '.api_version')" >> "$GITHUB_OUTPUT" - name: Import GPG key id: import_gpg - uses: crazy-max/ghaction-import-gpg@v5 + uses: crazy-max/ghaction-import-gpg@v6 with: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} passphrase: ${{ secrets.GPG_PASSPHRASE }} - name: Run GoReleaser - uses: goreleaser/goreleaser-action@5fdedb94abba051217030cc86d4523cf3f02243d # v4.6.0 + uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0 with: version: latest args: release --rm-dist diff --git a/builder/linode/config_test.go b/builder/linode/config_test.go new file mode 100644 index 00000000..7d96f905 --- /dev/null +++ b/builder/linode/config_test.go @@ -0,0 +1,93 @@ +package linode + +import ( + "testing" + "time" + + "github.com/hashicorp/packer-plugin-sdk/communicator" + "github.com/hashicorp/packer-plugin-sdk/template/interpolate" +) + +func TestPrepare(t *testing.T) { + data := communicator.SSH{ + SSHUsername: "root", + // more variables can be added depending on test scentarios + } + + config := &Config{ + ctx: interpolate.Context{}, + Comm: communicator.Config{SSH: data}, + PersonalAccessToken: "test-linode-access-token", + Region: "us-east", + InstanceType: "g6-standard-1", + Image: "linode/debian10", + } + + warnings, err := config.Prepare() + if err != nil { + t.Errorf("Prepare failed with error: %v", err) + } + + if len(warnings) > 0 { + t.Logf("Warnings during Prepare: %v", warnings) + } + + expectedStateTimeout := 5 * time.Minute + if config.StateTimeout != expectedStateTimeout { + t.Errorf("Expected StateTimeout: %v, Got: %v", expectedStateTimeout, config.StateTimeout) + } + + expectedImageCreateTimeout := 10 * time.Minute + if config.ImageCreateTimeout != expectedImageCreateTimeout { + t.Errorf("Expected ImageCreateTimeout: %v, Got: %v", expectedImageCreateTimeout, config.ImageCreateTimeout) + } +} + +func TestHCL2Spec(t *testing.T) { + packerBuildName := "testBuildName" + sshHost := "test-host" + + flatConfig := &FlatConfig{ + PackerBuildName: &packerBuildName, + SSHHost: &sshHost, + } + + hclSpec := flatConfig.HCL2Spec() + + expectedAttributes := []string{ + "packer_build_name", + "ssh_host", + } + + for _, attr := range expectedAttributes { + if _, exists := hclSpec[attr]; !exists { + t.Errorf("Expected attribute %s in HCL spec, but it's missing", attr) + } + } +} + +func TestHCL2SpecInterface(t *testing.T) { + purpose := "eth0" + label := "PrimaryInterfaceLabel" + ipamAddress := "192.168.1.10" + + flatIface := &FlatInterface{ + Purpose: &purpose, + Label: &label, + IPAMAddress: &ipamAddress, + } + + hclSpec := flatIface.HCL2Spec() + + expectedAttributes := []string{ + "purpose", + "label", + "ipam_address", + } + + for _, attr := range expectedAttributes { + if _, exists := hclSpec[attr]; !exists { + t.Errorf("Expected attribute %s in HCL spec, but it's missing", attr) + } + } +} diff --git a/go.mod b/go.mod index b21cea67..12814735 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,10 @@ go 1.19 require ( github.com/hashicorp/hcl/v2 v2.16.2 github.com/hashicorp/packer-plugin-sdk v0.5.1 - github.com/linode/linodego v1.21.0 + github.com/linode/linodego v1.23.0 github.com/zclconf/go-cty v1.12.1 - golang.org/x/crypto v0.12.0 - golang.org/x/oauth2 v0.11.0 + golang.org/x/crypto v0.14.0 + golang.org/x/oauth2 v0.13.0 ) require github.com/mitchellh/mapstructure v1.4.1 @@ -82,10 +82,10 @@ require ( github.com/ugorji/go/codec v1.2.6 // indirect github.com/ulikunitz/xz v0.5.10 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/net v0.14.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/term v0.11.0 // indirect - golang.org/x/text v0.12.0 // indirect + golang.org/x/net v0.16.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.126.0 // indirect diff --git a/go.sum b/go.sum index 708a77fd..145a3999 100644 --- a/go.sum +++ b/go.sum @@ -289,8 +289,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/linode/linodego v1.21.0 h1:VIco962oysKyNM7xkFGto+6ZwsepKbxeHDGBTLlqB4w= -github.com/linode/linodego v1.21.0/go.mod h1:eDc/1GxYzNsNfDTC/8QKuMJNkdfN/ndYwldupf4ue1M= +github.com/linode/linodego v1.23.0 h1:s0ReCZtuN9Z1IoUN9w1RLeYO1dMZUGPwOQ/IBFsBHtU= +github.com/linode/linodego v1.23.0/go.mod h1:0U7wj/UQOqBNbKv1FYTXiBUXueR8DY4HvIotwE0ENgg= github.com/masterzen/simplexml v0.0.0-20160608183007-4572e39b1ab9/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc= github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786 h1:2ZKn+w/BJeL43sCxI2jhPLRv73oVVOjEKZjKkflyqxg= github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc= @@ -419,8 +419,8 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0= github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ= @@ -448,8 +448,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -483,12 +483,12 @@ golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos= +golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= -golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= +golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= +golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -530,20 +530,20 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= -golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= diff --git a/scripts/add_to_xml_test_report.py b/scripts/add_to_xml_test_report.py new file mode 100644 index 00000000..41b4cef1 --- /dev/null +++ b/scripts/add_to_xml_test_report.py @@ -0,0 +1,37 @@ +import argparse +import xml.etree.ElementTree as ET + +# Parse command-line arguments +parser = argparse.ArgumentParser(description='Modify XML with workflow information') +parser.add_argument('--branch_name', required=True) +parser.add_argument('--gha_run_id', required=True) +parser.add_argument('--gha_run_number', required=True) +parser.add_argument('--xmlfile', required=True) # Added argument for XML file path + +args = parser.parse_args() + +# Open and parse the XML file +xml_file_path = args.xmlfile +tree = ET.parse(xml_file_path) +root = tree.getroot() + +# Create new elements for the information +branch_name_element = ET.Element('branch_name') +branch_name_element.text = args.branch_name + +gha_run_id_element = ET.Element('gha_run_id') +gha_run_id_element.text = args.gha_run_id + +gha_run_number_element = ET.Element('gha_run_number') +gha_run_number_element.text = args.gha_run_number + +# Add the new elements to the root of the XML +root.append(branch_name_element) +root.append(gha_run_id_element) +root.append(gha_run_number_element) + +# Save the modified XML +modified_xml_file_path = xml_file_path # Overwrite it +tree.write(modified_xml_file_path) + +print(f'Modified XML saved to {modified_xml_file_path}') diff --git a/scripts/test_report_upload_script.py b/scripts/test_report_upload_script.py new file mode 100644 index 00000000..6619f20b --- /dev/null +++ b/scripts/test_report_upload_script.py @@ -0,0 +1,41 @@ +import boto3 +import sys +import os +from botocore.exceptions import NoCredentialsError + +ACCESS_KEY = os.environ.get('LINODE_CLI_OBJ_ACCESS_KEY') +SECRET_KEY = os.environ.get('LINODE_CLI_OBJ_SECRET_KEY') +BUCKET_NAME = 'dx-test-results' + +linode_obj_config = { + "aws_access_key_id": ACCESS_KEY, + "aws_secret_access_key": SECRET_KEY, + "endpoint_url": "https://us-southeast-1.linodeobjects.com", + "region_name": "us-southeast-1", +} + + +def upload_to_linode_object_storage(file_name): + try: + s3 = boto3.client('s3', **linode_obj_config) + + s3.upload_file(Filename=file_name, Bucket=BUCKET_NAME, Key=file_name) + + print(f'Successfully uploaded {file_name} to Linode Object Storage.') + + except NoCredentialsError: + print('Credentials not available. Ensure you have set your AWS credentials.') + + +if __name__ == '__main__': + if len(sys.argv) != 2: + print('Usage: python upload_to_linode.py ') + sys.exit(1) + + file_name = sys.argv[1] + + if not file_name: + print('Error: The provided file name is empty or invalid.') + sys.exit(1) + + upload_to_linode_object_storage(file_name) \ No newline at end of file